Initial push of Plunk Next
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:55432/postgres
|
||||
DIRECT_DATABASE_URL=postgresql://postgres:postgres@localhost:55432/postgres
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@plunk/db",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"db:generate": "prisma generate",
|
||||
"migrate:dev": "prisma migrate dev",
|
||||
"migrate:prod": "prisma migrate deploy",
|
||||
"clean": "rimraf node_modules .turbo dist",
|
||||
"build": "tsc && prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.19.0",
|
||||
"prisma": "^6.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AuthMethod" AS ENUM ('PASSWORD', 'GOOGLE_OAUTH', 'GITHUB_OAUTH');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TemplateType" AS ENUM ('TRANSACTIONAL', 'MARKETING');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CampaignStatus" AS ENUM ('DRAFT', 'SCHEDULED', 'SENDING', 'SENT', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "CampaignAudienceType" AS ENUM ('ALL', 'FILTERED', 'SEGMENT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WorkflowTriggerType" AS ENUM ('EVENT', 'MANUAL', 'SCHEDULE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WorkflowStepType" AS ENUM ('TRIGGER', 'SEND_EMAIL', 'DELAY', 'WAIT_FOR_EVENT', 'CONDITION', 'EXIT', 'WEBHOOK', 'UPDATE_CONTACT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WorkflowExecutionStatus" AS ENUM ('RUNNING', 'WAITING', 'COMPLETED', 'EXITED', 'FAILED', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "StepExecutionStatus" AS ENUM ('PENDING', 'SCHEDULED', 'WAITING', 'RUNNING', 'COMPLETED', 'SKIPPED', 'FAILED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EmailSourceType" AS ENUM ('TRANSACTIONAL', 'CAMPAIGN', 'WORKFLOW');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EmailStatus" AS ENUM ('PENDING', 'SENDING', 'SENT', 'DELIVERED', 'OPENED', 'CLICKED', 'BOUNCED', 'COMPLAINED', 'FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT,
|
||||
"type" "AuthMethod" NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "projects" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"public" TEXT NOT NULL,
|
||||
"secret" TEXT NOT NULL,
|
||||
"disabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"customer" TEXT,
|
||||
"subscription" TEXT,
|
||||
"billingLimitWorkflows" INTEGER,
|
||||
"billingLimitCampaigns" INTEGER,
|
||||
"billingLimitTransactional" INTEGER,
|
||||
"trackingEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "projects_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "memberships" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"role" "Role" NOT NULL DEFAULT 'MEMBER',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "memberships_pkey" PRIMARY KEY ("userId","projectId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "domains" (
|
||||
"id" TEXT NOT NULL,
|
||||
"domain" TEXT NOT NULL,
|
||||
"verified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dkimTokens" JSONB,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "domains_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "contacts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"data" JSONB,
|
||||
"subscribed" BOOLEAN NOT NULL DEFAULT true,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "contacts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "templates" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"subject" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"fromName" TEXT,
|
||||
"replyTo" TEXT,
|
||||
"type" "TemplateType" NOT NULL DEFAULT 'MARKETING',
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "templates_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "segments" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"filters" JSONB NOT NULL,
|
||||
"trackMembership" BOOLEAN NOT NULL DEFAULT false,
|
||||
"memberCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "segments_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "segment_memberships" (
|
||||
"contactId" TEXT NOT NULL,
|
||||
"segmentId" TEXT NOT NULL,
|
||||
"enteredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"exitedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "segment_memberships_pkey" PRIMARY KEY ("contactId","segmentId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "campaigns" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" "CampaignStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"subject" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"fromName" TEXT,
|
||||
"replyTo" TEXT,
|
||||
"audienceType" "CampaignAudienceType" NOT NULL DEFAULT 'ALL',
|
||||
"audienceFilter" JSONB,
|
||||
"segmentId" TEXT,
|
||||
"scheduledFor" TIMESTAMP(3),
|
||||
"totalRecipients" INTEGER NOT NULL DEFAULT 0,
|
||||
"sentCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"deliveredCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"openedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"clickedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"bouncedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"sentAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "campaigns_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workflows" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"triggerType" "WorkflowTriggerType" NOT NULL,
|
||||
"triggerConfig" JSONB,
|
||||
"allowReentry" BOOLEAN NOT NULL DEFAULT false,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "workflows_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workflow_steps" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" "WorkflowStepType" NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"position" JSONB NOT NULL,
|
||||
"config" JSONB NOT NULL,
|
||||
"workflowId" TEXT NOT NULL,
|
||||
"templateId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "workflow_steps_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workflow_transitions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"fromStepId" TEXT NOT NULL,
|
||||
"toStepId" TEXT NOT NULL,
|
||||
"condition" JSONB,
|
||||
"priority" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "workflow_transitions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workflow_executions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"workflowId" TEXT NOT NULL,
|
||||
"contactId" TEXT NOT NULL,
|
||||
"status" "WorkflowExecutionStatus" NOT NULL DEFAULT 'RUNNING',
|
||||
"currentStepId" TEXT,
|
||||
"exitReason" TEXT,
|
||||
"context" JSONB,
|
||||
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "workflow_executions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "workflow_step_executions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"executionId" TEXT NOT NULL,
|
||||
"stepId" TEXT NOT NULL,
|
||||
"status" "StepExecutionStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"scheduledFor" TIMESTAMP(3),
|
||||
"executeAfter" TIMESTAMP(3),
|
||||
"output" JSONB,
|
||||
"error" TEXT,
|
||||
"startedAt" TIMESTAMP(3),
|
||||
"completedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "workflow_step_executions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "emails" (
|
||||
"id" TEXT NOT NULL,
|
||||
"contactId" TEXT NOT NULL,
|
||||
"subject" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"fromName" TEXT,
|
||||
"replyTo" TEXT,
|
||||
"headers" JSONB,
|
||||
"attachments" JSONB,
|
||||
"messageId" TEXT,
|
||||
"sourceType" "EmailSourceType" NOT NULL,
|
||||
"templateId" TEXT,
|
||||
"campaignId" TEXT,
|
||||
"workflowExecutionId" TEXT,
|
||||
"workflowStepExecutionId" TEXT,
|
||||
"status" "EmailStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"sentAt" TIMESTAMP(3),
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"openedAt" TIMESTAMP(3),
|
||||
"clickedAt" TIMESTAMP(3),
|
||||
"bouncedAt" TIMESTAMP(3),
|
||||
"complainedAt" TIMESTAMP(3),
|
||||
"opens" INTEGER NOT NULL DEFAULT 0,
|
||||
"clicks" INTEGER NOT NULL DEFAULT 0,
|
||||
"error" TEXT,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "emails_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"data" JSONB,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"contactId" TEXT,
|
||||
"emailId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "api_requests" (
|
||||
"id" TEXT NOT NULL,
|
||||
"method" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"statusCode" INTEGER NOT NULL,
|
||||
"duration" INTEGER NOT NULL,
|
||||
"projectId" TEXT,
|
||||
"userId" TEXT,
|
||||
"authType" TEXT,
|
||||
"ip" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"errorCode" TEXT,
|
||||
"errorMessage" TEXT,
|
||||
"requestSize" INTEGER,
|
||||
"responseSize" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "api_requests_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "projects_public_key" ON "projects"("public");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "projects_secret_key" ON "projects"("secret");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "projects_customer_key" ON "projects"("customer");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "projects_subscription_key" ON "projects"("subscription");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "domains_projectId_idx" ON "domains"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "domains_projectId_verified_idx" ON "domains"("projectId", "verified");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "domains_projectId_domain_key" ON "domains"("projectId", "domain");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "contacts_projectId_idx" ON "contacts"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "contacts_projectId_subscribed_idx" ON "contacts"("projectId", "subscribed");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "contacts_projectId_email_key" ON "contacts"("projectId", "email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "templates_projectId_idx" ON "templates"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "templates_projectId_type_idx" ON "templates"("projectId", "type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "segments_projectId_idx" ON "segments"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "segment_memberships_segmentId_exitedAt_idx" ON "segment_memberships"("segmentId", "exitedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "segment_memberships_contactId_exitedAt_idx" ON "segment_memberships"("contactId", "exitedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "segment_memberships_enteredAt_idx" ON "segment_memberships"("enteredAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "campaigns_projectId_status_idx" ON "campaigns"("projectId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "campaigns_scheduledFor_idx" ON "campaigns"("scheduledFor");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "campaigns_segmentId_idx" ON "campaigns"("segmentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflows_projectId_idx" ON "workflows"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflows_projectId_enabled_idx" ON "workflows"("projectId", "enabled");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_steps_workflowId_idx" ON "workflow_steps"("workflowId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_steps_templateId_idx" ON "workflow_steps"("templateId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_transitions_fromStepId_idx" ON "workflow_transitions"("fromStepId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_transitions_toStepId_idx" ON "workflow_transitions"("toStepId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_executions_workflowId_contactId_idx" ON "workflow_executions"("workflowId", "contactId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_executions_workflowId_status_idx" ON "workflow_executions"("workflowId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_executions_contactId_status_idx" ON "workflow_executions"("contactId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_executions_status_currentStepId_idx" ON "workflow_executions"("status", "currentStepId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_step_executions_executionId_status_idx" ON "workflow_step_executions"("executionId", "status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_step_executions_stepId_idx" ON "workflow_step_executions"("stepId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_step_executions_status_scheduledFor_idx" ON "workflow_step_executions"("status", "scheduledFor");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "workflow_step_executions_scheduledFor_idx" ON "workflow_step_executions"("scheduledFor");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "emails_messageId_key" ON "emails"("messageId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_projectId_contactId_idx" ON "emails"("projectId", "contactId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_contactId_idx" ON "emails"("contactId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_campaignId_idx" ON "emails"("campaignId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_workflowExecutionId_idx" ON "emails"("workflowExecutionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_workflowStepExecutionId_idx" ON "emails"("workflowStepExecutionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_status_idx" ON "emails"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_createdAt_idx" ON "emails"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "emails_projectId_sourceType_createdAt_idx" ON "emails"("projectId", "sourceType", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_projectId_name_idx" ON "events"("projectId", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_contactId_idx" ON "events"("contactId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_emailId_idx" ON "events"("emailId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "events_createdAt_idx" ON "events"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_requests_projectId_createdAt_idx" ON "api_requests"("projectId", "createdAt" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_requests_userId_createdAt_idx" ON "api_requests"("userId", "createdAt" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_requests_statusCode_createdAt_idx" ON "api_requests"("statusCode", "createdAt" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_requests_path_createdAt_idx" ON "api_requests"("path", "createdAt" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_requests_createdAt_idx" ON "api_requests"("createdAt" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "api_requests_projectId_statusCode_createdAt_idx" ON "api_requests"("projectId", "statusCode", "createdAt" DESC);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "memberships" ADD CONSTRAINT "memberships_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "memberships" ADD CONSTRAINT "memberships_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "domains" ADD CONSTRAINT "domains_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "contacts" ADD CONSTRAINT "contacts_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "templates" ADD CONSTRAINT "templates_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "segments" ADD CONSTRAINT "segments_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "segment_memberships" ADD CONSTRAINT "segment_memberships_contactId_fkey" FOREIGN KEY ("contactId") REFERENCES "contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "segment_memberships" ADD CONSTRAINT "segment_memberships_segmentId_fkey" FOREIGN KEY ("segmentId") REFERENCES "segments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "campaigns" ADD CONSTRAINT "campaigns_segmentId_fkey" FOREIGN KEY ("segmentId") REFERENCES "segments"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "campaigns" ADD CONSTRAINT "campaigns_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflows" ADD CONSTRAINT "workflows_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_steps" ADD CONSTRAINT "workflow_steps_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflows"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_steps" ADD CONSTRAINT "workflow_steps_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_transitions" ADD CONSTRAINT "workflow_transitions_fromStepId_fkey" FOREIGN KEY ("fromStepId") REFERENCES "workflow_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_transitions" ADD CONSTRAINT "workflow_transitions_toStepId_fkey" FOREIGN KEY ("toStepId") REFERENCES "workflow_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_executions" ADD CONSTRAINT "workflow_executions_workflowId_fkey" FOREIGN KEY ("workflowId") REFERENCES "workflows"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_executions" ADD CONSTRAINT "workflow_executions_contactId_fkey" FOREIGN KEY ("contactId") REFERENCES "contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_executions" ADD CONSTRAINT "workflow_executions_currentStepId_fkey" FOREIGN KEY ("currentStepId") REFERENCES "workflow_steps"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_step_executions" ADD CONSTRAINT "workflow_step_executions_executionId_fkey" FOREIGN KEY ("executionId") REFERENCES "workflow_executions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "workflow_step_executions" ADD CONSTRAINT "workflow_step_executions_stepId_fkey" FOREIGN KEY ("stepId") REFERENCES "workflow_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "emails" ADD CONSTRAINT "emails_contactId_fkey" FOREIGN KEY ("contactId") REFERENCES "contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "emails" ADD CONSTRAINT "emails_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "emails" ADD CONSTRAINT "emails_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES "campaigns"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "emails" ADD CONSTRAINT "emails_workflowExecutionId_fkey" FOREIGN KEY ("workflowExecutionId") REFERENCES "workflow_executions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "emails" ADD CONSTRAINT "emails_workflowStepExecutionId_fkey" FOREIGN KEY ("workflowStepExecutionId") REFERENCES "workflow_step_executions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "emails" ADD CONSTRAINT "emails_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "events" ADD CONSTRAINT "events_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "events" ADD CONSTRAINT "events_contactId_fkey" FOREIGN KEY ("contactId") REFERENCES "contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "events" ADD CONSTRAINT "events_emailId_fkey" FOREIGN KEY ("emailId") REFERENCES "emails"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "api_requests" ADD CONSTRAINT "api_requests_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "projects"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -0,0 +1,727 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl-openssl-3.0.x", "linux-musl-arm64-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_DATABASE_URL")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CORE MODELS
|
||||
// ============================================
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Credentials
|
||||
email String @unique
|
||||
password String?
|
||||
type AuthMethod
|
||||
|
||||
// Relations
|
||||
memberships Membership[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Details
|
||||
name String
|
||||
|
||||
// API
|
||||
public String @unique
|
||||
secret String @unique
|
||||
|
||||
// Admin
|
||||
disabled Boolean @default(false)
|
||||
|
||||
// Billing
|
||||
customer String? @unique
|
||||
subscription String? @unique
|
||||
|
||||
// Billing Limits (per calendar month, null = unlimited)
|
||||
billingLimitWorkflows Int? // Max workflow emails per month
|
||||
billingLimitCampaigns Int? // Max campaign emails per month
|
||||
billingLimitTransactional Int? // Max transactional emails per month
|
||||
|
||||
// Email Tracking
|
||||
trackingEnabled Boolean @default(true) // Enable/disable open and click tracking
|
||||
|
||||
// Relations
|
||||
members Membership[]
|
||||
contacts Contact[]
|
||||
templates Template[]
|
||||
segments Segment[]
|
||||
workflows Workflow[]
|
||||
campaigns Campaign[]
|
||||
emails Email[]
|
||||
events Event[]
|
||||
domains Domain[]
|
||||
apiRequests ApiRequest[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("projects")
|
||||
}
|
||||
|
||||
model Membership {
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
projectId String
|
||||
|
||||
// Details
|
||||
role Role @default(MEMBER)
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@id([userId, projectId])
|
||||
@@map("memberships")
|
||||
}
|
||||
|
||||
model Domain {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Domain details
|
||||
domain String
|
||||
verified Boolean @default(false)
|
||||
|
||||
// DKIM tokens for DNS verification
|
||||
dkimTokens Json? // Array of DKIM token strings
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([projectId, domain])
|
||||
@@index([projectId])
|
||||
@@index([projectId, verified])
|
||||
@@map("domains")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CONTACTS & TEMPLATES
|
||||
// ============================================
|
||||
|
||||
model Contact {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Details
|
||||
email String
|
||||
data Json? // Custom fields: { firstName: "John", plan: "pro", ... }
|
||||
|
||||
// Subscription
|
||||
subscribed Boolean @default(true)
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
emails Email[]
|
||||
workflowExecutions WorkflowExecution[]
|
||||
events Event[]
|
||||
segmentMemberships SegmentMembership[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([projectId, email])
|
||||
@@index([projectId])
|
||||
@@index([projectId, subscribed])
|
||||
@@map("contacts")
|
||||
}
|
||||
|
||||
model Template {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Details
|
||||
name String
|
||||
description String?
|
||||
|
||||
// Content
|
||||
subject String
|
||||
body String // HTML content with {{variable}} placeholders
|
||||
from String
|
||||
fromName String?
|
||||
replyTo String?
|
||||
|
||||
// Type
|
||||
type TemplateType @default(MARKETING)
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
emails Email[]
|
||||
workflowSteps WorkflowStep[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([projectId])
|
||||
@@index([projectId, type])
|
||||
@@map("templates")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// SEGMENTS (Dynamic audience groups)
|
||||
// ============================================
|
||||
|
||||
model Segment {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Details
|
||||
name String
|
||||
description String?
|
||||
|
||||
// Filter conditions (evaluated dynamically)
|
||||
filters Json
|
||||
// Array of conditions with AND/OR logic:
|
||||
// [
|
||||
// { field: "data.plan", operator: "equals", value: "FREE" },
|
||||
// { field: "subscribed", operator: "equals", value: true },
|
||||
// { field: "emails.openedAt", operator: "within", value: 30, unit: "days" }
|
||||
// ]
|
||||
// Operators: equals, notEquals, contains, greaterThan, lessThan, within, exists, etc.
|
||||
|
||||
// Track membership changes (enables segment entry/exit events)
|
||||
trackMembership Boolean @default(false)
|
||||
|
||||
// Stats (computed)
|
||||
memberCount Int @default(0)
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
memberships SegmentMembership[]
|
||||
campaigns Campaign[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([projectId])
|
||||
@@map("segments")
|
||||
}
|
||||
|
||||
model SegmentMembership {
|
||||
// Relations
|
||||
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
||||
contactId String
|
||||
|
||||
segment Segment @relation(fields: [segmentId], references: [id], onDelete: Cascade)
|
||||
segmentId String
|
||||
|
||||
// Membership tracking
|
||||
enteredAt DateTime @default(now())
|
||||
exitedAt DateTime? // null = currently in segment
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@id([contactId, segmentId])
|
||||
@@index([segmentId, exitedAt]) // Active members (exitedAt = null)
|
||||
@@index([contactId, exitedAt])
|
||||
@@index([enteredAt])
|
||||
@@map("segment_memberships")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CAMPAIGNS (One-time broadcasts)
|
||||
// ============================================
|
||||
|
||||
model Campaign {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Details
|
||||
name String
|
||||
description String?
|
||||
status CampaignStatus @default(DRAFT)
|
||||
|
||||
// Email content
|
||||
subject String
|
||||
body String @db.Text
|
||||
from String
|
||||
fromName String?
|
||||
replyTo String?
|
||||
|
||||
// Audience selection
|
||||
audienceType CampaignAudienceType @default(ALL)
|
||||
audienceFilter Json? // For FILTERED: manual filter conditions
|
||||
|
||||
segment Segment? @relation(fields: [segmentId], references: [id])
|
||||
segmentId String? // For SEGMENT: reference to saved segment
|
||||
|
||||
// Scheduling
|
||||
scheduledFor DateTime?
|
||||
|
||||
// Stats (computed)
|
||||
totalRecipients Int @default(0)
|
||||
sentCount Int @default(0)
|
||||
deliveredCount Int @default(0)
|
||||
openedCount Int @default(0)
|
||||
clickedCount Int @default(0)
|
||||
bouncedCount Int @default(0)
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
emails Email[]
|
||||
|
||||
// Timestamps
|
||||
sentAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([projectId, status])
|
||||
@@index([scheduledFor])
|
||||
@@index([segmentId])
|
||||
@@map("campaigns")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// WORKFLOWS (Automated sequences)
|
||||
// ============================================
|
||||
|
||||
model Workflow {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Details
|
||||
name String
|
||||
description String?
|
||||
enabled Boolean @default(false)
|
||||
|
||||
// Entry point
|
||||
triggerType WorkflowTriggerType
|
||||
triggerConfig Json? // { eventName: "user.signup" } or { schedule: "0 9 * * *" }
|
||||
|
||||
// Re-entry behavior
|
||||
allowReentry Boolean @default(false) // If true, contacts can enter workflow multiple times
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
steps WorkflowStep[]
|
||||
executions WorkflowExecution[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([projectId])
|
||||
@@index([projectId, enabled])
|
||||
@@map("workflows")
|
||||
}
|
||||
|
||||
model WorkflowStep {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Step details
|
||||
type WorkflowStepType
|
||||
name String
|
||||
position Json // { x: 100, y: 200 } for visual editor
|
||||
|
||||
// Step configuration (flexible JSON)
|
||||
config Json
|
||||
// Examples by type:
|
||||
// TRIGGER: { eventName: "user.signup" }
|
||||
// SEND_EMAIL: { templateId: "uuid", from: "[email protected]" }
|
||||
// DELAY: { amount: 24, unit: "hours" }
|
||||
// WAIT_FOR_EVENT: { eventName: "email.clicked", timeout: 86400 }
|
||||
// CONDITION: { field: "contact.data.plan", operator: "equals", value: "pro" }
|
||||
// EXIT: { reason: "unsubscribed" }
|
||||
|
||||
// Relations
|
||||
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
||||
workflowId String
|
||||
|
||||
template Template? @relation(fields: [templateId], references: [id])
|
||||
templateId String? // For SEND_EMAIL steps
|
||||
|
||||
// Graph connections
|
||||
outgoingTransitions WorkflowTransition[] @relation("FromStep")
|
||||
incomingTransitions WorkflowTransition[] @relation("ToStep")
|
||||
|
||||
// Execution tracking
|
||||
executions WorkflowStepExecution[]
|
||||
currentWorkflowExecutions WorkflowExecution[] // Executions currently on this step
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([workflowId])
|
||||
@@index([templateId])
|
||||
@@map("workflow_steps")
|
||||
}
|
||||
|
||||
model WorkflowTransition {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// From -> To
|
||||
fromStep WorkflowStep @relation("FromStep", fields: [fromStepId], references: [id], onDelete: Cascade)
|
||||
fromStepId String
|
||||
|
||||
toStep WorkflowStep @relation("ToStep", fields: [toStepId], references: [id], onDelete: Cascade)
|
||||
toStepId String
|
||||
|
||||
// Conditional routing
|
||||
condition Json? // null = always follow, or { branch: "yes" } for condition steps
|
||||
priority Int @default(0) // Order to evaluate transitions
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([fromStepId])
|
||||
@@index([toStepId])
|
||||
@@map("workflow_transitions")
|
||||
}
|
||||
|
||||
model WorkflowExecution {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relations
|
||||
workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
|
||||
workflowId String
|
||||
|
||||
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
||||
contactId String
|
||||
|
||||
// Execution state
|
||||
status WorkflowExecutionStatus @default(RUNNING)
|
||||
|
||||
// Current position
|
||||
currentStep WorkflowStep? @relation(fields: [currentStepId], references: [id])
|
||||
currentStepId String?
|
||||
|
||||
// Exit information
|
||||
exitReason String?
|
||||
|
||||
// Context data (merged with contact.data when executing)
|
||||
context Json? // Additional variables for this execution
|
||||
|
||||
// Relations
|
||||
stepExecutions WorkflowStepExecution[]
|
||||
emails Email[]
|
||||
|
||||
// Timestamps
|
||||
startedAt DateTime @default(now())
|
||||
completedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([workflowId, contactId]) // Index for querying executions by workflow and contact
|
||||
@@index([workflowId, status])
|
||||
@@index([contactId, status])
|
||||
@@index([status, currentStepId])
|
||||
@@map("workflow_executions")
|
||||
}
|
||||
|
||||
model WorkflowStepExecution {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Relations
|
||||
execution WorkflowExecution @relation(fields: [executionId], references: [id], onDelete: Cascade)
|
||||
executionId String
|
||||
|
||||
step WorkflowStep @relation(fields: [stepId], references: [id], onDelete: Cascade)
|
||||
stepId String
|
||||
|
||||
// Execution state
|
||||
status StepExecutionStatus @default(PENDING)
|
||||
|
||||
// Delay/scheduling
|
||||
scheduledFor DateTime? // When this step should execute (for DELAY steps)
|
||||
executeAfter DateTime? // Don't execute before this time (for WAIT_FOR_EVENT timeout)
|
||||
|
||||
// Result tracking
|
||||
output Json? // Step execution result
|
||||
error String?
|
||||
|
||||
// Relations
|
||||
emails Email[] // Emails sent by this step execution
|
||||
|
||||
// Timestamps
|
||||
startedAt DateTime?
|
||||
completedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([executionId, status])
|
||||
@@index([stepId])
|
||||
@@index([status, scheduledFor]) // For delay queue processor
|
||||
@@index([scheduledFor])
|
||||
@@map("workflow_step_executions")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// EMAILS (Unified tracking for all sends)
|
||||
// ============================================
|
||||
|
||||
model Email {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Recipient
|
||||
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
||||
contactId String
|
||||
|
||||
// Content (denormalized for history)
|
||||
subject String
|
||||
body String // Rendered HTML
|
||||
from String
|
||||
fromName String?
|
||||
replyTo String?
|
||||
headers Json? // Custom email headers
|
||||
attachments Json? // Array of {filename: string, content: string (base64), contentType: string}
|
||||
|
||||
// AWS SES Message ID (for tracking webhooks)
|
||||
messageId String? @unique
|
||||
|
||||
// Source - identifies how this email was triggered
|
||||
sourceType EmailSourceType
|
||||
|
||||
// Relations to source (one will be set based on sourceType)
|
||||
template Template? @relation(fields: [templateId], references: [id])
|
||||
templateId String? // For TRANSACTIONAL
|
||||
|
||||
campaign Campaign? @relation(fields: [campaignId], references: [id], onDelete: Cascade)
|
||||
campaignId String? // For CAMPAIGN
|
||||
|
||||
workflowExecution WorkflowExecution? @relation(fields: [workflowExecutionId], references: [id], onDelete: Cascade)
|
||||
workflowExecutionId String? // For WORKFLOW
|
||||
|
||||
workflowStepExecution WorkflowStepExecution? @relation(fields: [workflowStepExecutionId], references: [id], onDelete: Cascade)
|
||||
workflowStepExecutionId String? // For WORKFLOW (specific step)
|
||||
|
||||
// Delivery
|
||||
status EmailStatus @default(PENDING)
|
||||
|
||||
// Event timestamps
|
||||
sentAt DateTime?
|
||||
deliveredAt DateTime?
|
||||
openedAt DateTime? // First open
|
||||
clickedAt DateTime? // First click
|
||||
bouncedAt DateTime?
|
||||
complainedAt DateTime?
|
||||
|
||||
// Event counts
|
||||
opens Int @default(0)
|
||||
clicks Int @default(0)
|
||||
|
||||
// Error tracking
|
||||
error String?
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
events Event[]
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([projectId, contactId])
|
||||
@@index([contactId])
|
||||
@@index([campaignId])
|
||||
@@index([workflowExecutionId])
|
||||
@@index([workflowStepExecutionId])
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
@@index([projectId, sourceType, createdAt]) // For billing limit queries
|
||||
@@map("emails")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// EVENTS (Webhook tracking & workflow triggers)
|
||||
// ============================================
|
||||
|
||||
model Event {
|
||||
id String @id @default(uuid())
|
||||
|
||||
// Event details
|
||||
name String // e.g., "email.opened", "email.clicked", "user.signup"
|
||||
data Json? // Event payload
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
projectId String
|
||||
|
||||
contact Contact? @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
||||
contactId String?
|
||||
|
||||
email Email? @relation(fields: [emailId], references: [id], onDelete: Cascade)
|
||||
emailId String?
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([projectId, name])
|
||||
@@index([contactId])
|
||||
@@index([emailId])
|
||||
@@index([createdAt])
|
||||
@@map("events")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ENUMS
|
||||
// ============================================
|
||||
|
||||
enum AuthMethod {
|
||||
PASSWORD
|
||||
GOOGLE_OAUTH
|
||||
GITHUB_OAUTH
|
||||
}
|
||||
|
||||
enum Role {
|
||||
OWNER
|
||||
ADMIN
|
||||
MEMBER
|
||||
}
|
||||
|
||||
enum TemplateType {
|
||||
TRANSACTIONAL
|
||||
MARKETING
|
||||
}
|
||||
|
||||
enum CampaignStatus {
|
||||
DRAFT
|
||||
SCHEDULED
|
||||
SENDING
|
||||
SENT
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum CampaignAudienceType {
|
||||
ALL // All contacts
|
||||
FILTERED // Based on audienceFilter
|
||||
SEGMENT // Predefined segment
|
||||
}
|
||||
|
||||
enum WorkflowTriggerType {
|
||||
EVENT // Triggered by an event (e.g., user.signup)
|
||||
MANUAL // Manually started via API
|
||||
SCHEDULE // Runs on a schedule (cron)
|
||||
}
|
||||
|
||||
enum WorkflowStepType {
|
||||
TRIGGER // Entry point
|
||||
SEND_EMAIL // Send an email
|
||||
DELAY // Wait for X time
|
||||
WAIT_FOR_EVENT // Wait for specific event (with timeout)
|
||||
CONDITION // If/else branching
|
||||
EXIT // Early exit point
|
||||
WEBHOOK // Call external webhook
|
||||
UPDATE_CONTACT // Update contact fields
|
||||
}
|
||||
|
||||
enum WorkflowExecutionStatus {
|
||||
RUNNING // Currently executing
|
||||
WAITING // Waiting for event or delay
|
||||
COMPLETED // Finished normally
|
||||
EXITED // Exited early (via EXIT step)
|
||||
FAILED // Failed with error
|
||||
CANCELLED // Manually cancelled
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// API REQUEST LOGGING
|
||||
// ============================================
|
||||
|
||||
model ApiRequest {
|
||||
id String @id // Request ID (UUID from X-Request-ID header)
|
||||
|
||||
// Request metadata
|
||||
method String // HTTP method (GET, POST, etc.)
|
||||
path String // Request path (/v1/send, /contacts, etc.)
|
||||
statusCode Int // HTTP response status code
|
||||
duration Int // Response time in milliseconds
|
||||
|
||||
// Authentication context
|
||||
projectId String? // Project that made the request (if authenticated)
|
||||
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
|
||||
userId String? // User that made the request (if JWT auth)
|
||||
authType String? // "apiKey" or "jwt"
|
||||
|
||||
// Request details
|
||||
ip String? // Client IP address
|
||||
userAgent String? // User-Agent header
|
||||
|
||||
// Error information (if status >= 400)
|
||||
errorCode String? // Machine-readable error code (VALIDATION_ERROR, etc.)
|
||||
errorMessage String? // Human-readable error message
|
||||
|
||||
// Size tracking (for analytics)
|
||||
requestSize Int? // Request body size in bytes
|
||||
responseSize Int? // Response body size in bytes
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Indexes for efficient querying
|
||||
@@index([projectId, createdAt(sort: Desc)]) // Query requests by project, most recent first
|
||||
@@index([userId, createdAt(sort: Desc)]) // Query requests by user
|
||||
@@index([statusCode, createdAt(sort: Desc)]) // Query errors (WHERE statusCode >= 400)
|
||||
@@index([path, createdAt(sort: Desc)]) // Query by endpoint
|
||||
@@index([createdAt(sort: Desc)]) // General time-based queries
|
||||
@@index([projectId, statusCode, createdAt(sort: Desc)]) // Composite for project error queries
|
||||
@@map("api_requests")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ENUMS
|
||||
// ============================================
|
||||
|
||||
enum StepExecutionStatus {
|
||||
PENDING // Not yet started
|
||||
SCHEDULED // Scheduled for future execution (DELAY)
|
||||
WAITING // Waiting for event (WAIT_FOR_EVENT)
|
||||
RUNNING // Currently executing
|
||||
COMPLETED // Successfully completed
|
||||
SKIPPED // Skipped (e.g., condition not met)
|
||||
FAILED // Failed with error
|
||||
}
|
||||
|
||||
enum EmailSourceType {
|
||||
TRANSACTIONAL // Sent via API call
|
||||
CAMPAIGN // Sent as part of broadcast
|
||||
WORKFLOW // Sent via workflow automation
|
||||
}
|
||||
|
||||
enum EmailStatus {
|
||||
PENDING // Queued for sending
|
||||
SENDING // Currently being sent
|
||||
SENT // Successfully sent to provider
|
||||
DELIVERED // Confirmed delivered
|
||||
OPENED // Recipient opened email
|
||||
CLICKED // Recipient clicked link
|
||||
BOUNCED // Bounced (hard or soft)
|
||||
COMPLAINED // Marked as spam
|
||||
FAILED // Failed to send
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@prisma/client';
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@plunk/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user