Initial BizOne portal setup
This commit is contained in:
30
prisma/migrations/0001_init/migration.sql
Normal file
30
prisma/migrations/0001_init/migration.sql
Normal file
@ -0,0 +1,30 @@
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE contacts (
|
||||
id UUID PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
phone_number TEXT NOT NULL UNIQUE,
|
||||
email TEXT,
|
||||
company TEXT,
|
||||
notes TEXT,
|
||||
is_blacklisted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE webhook_events (
|
||||
id UUID PRIMARY KEY,
|
||||
provider TEXT NOT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
payload_json JSONB NOT NULL,
|
||||
processing_status TEXT NOT NULL DEFAULT 'received',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
21
prisma/migrations/0002_webhook_runtime/migration.sql
Normal file
21
prisma/migrations/0002_webhook_runtime/migration.sql
Normal file
@ -0,0 +1,21 @@
|
||||
ALTER TABLE webhook_events
|
||||
ADD COLUMN event_id TEXT,
|
||||
ADD COLUMN sender_phone TEXT,
|
||||
ADD COLUMN recipient_phone TEXT,
|
||||
ADD COLUMN external_message_id TEXT,
|
||||
ADD COLUMN event_timestamp TIMESTAMP,
|
||||
ADD COLUMN verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN processing_notes TEXT,
|
||||
ADD COLUMN updated_at TIMESTAMP NOT NULL DEFAULT NOW();
|
||||
|
||||
UPDATE webhook_events
|
||||
SET event_id = id::text,
|
||||
event_timestamp = created_at
|
||||
WHERE event_id IS NULL
|
||||
OR event_timestamp IS NULL;
|
||||
|
||||
ALTER TABLE webhook_events
|
||||
ALTER COLUMN event_id SET NOT NULL,
|
||||
ALTER COLUMN event_timestamp SET NOT NULL;
|
||||
|
||||
CREATE UNIQUE INDEX webhook_events_event_id_key ON webhook_events(event_id);
|
||||
28
prisma/migrations/0003_jobs_and_integrations/migration.sql
Normal file
28
prisma/migrations/0003_jobs_and_integrations/migration.sql
Normal file
@ -0,0 +1,28 @@
|
||||
CREATE TABLE integration_configs (
|
||||
id UUID PRIMARY KEY,
|
||||
config_key TEXT NOT NULL UNIQUE,
|
||||
provider TEXT NOT NULL,
|
||||
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
config_json JSONB NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE jobs (
|
||||
id UUID PRIMARY KEY,
|
||||
queue_name TEXT NOT NULL,
|
||||
job_type TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'queued',
|
||||
payload_json JSONB NOT NULL,
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
max_attempts INTEGER NOT NULL DEFAULT 3,
|
||||
available_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
processed_at TIMESTAMP,
|
||||
failed_at TIMESTAMP,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX jobs_queue_name_status_available_at_idx
|
||||
ON jobs(queue_name, status, available_at);
|
||||
18
prisma/migrations/0004_audit_logs/migration.sql
Normal file
18
prisma/migrations/0004_audit_logs/migration.sql
Normal file
@ -0,0 +1,18 @@
|
||||
CREATE TABLE audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
actor_user_id TEXT,
|
||||
actor_name TEXT NOT NULL,
|
||||
actor_email TEXT,
|
||||
action_type TEXT NOT NULL,
|
||||
module TEXT NOT NULL,
|
||||
ip_address TEXT,
|
||||
severity TEXT NOT NULL DEFAULT 'default',
|
||||
details TEXT NOT NULL,
|
||||
metadata_json JSONB,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX audit_logs_created_at_idx ON audit_logs(created_at);
|
||||
CREATE INDEX audit_logs_actor_name_idx ON audit_logs(actor_name);
|
||||
CREATE INDEX audit_logs_action_type_idx ON audit_logs(action_type);
|
||||
CREATE INDEX audit_logs_module_idx ON audit_logs(module);
|
||||
21
prisma/migrations/0005_roles/migration.sql
Normal file
21
prisma/migrations/0005_roles/migration.sql
Normal file
@ -0,0 +1,21 @@
|
||||
CREATE TABLE roles (
|
||||
id TEXT PRIMARY KEY,
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
badge TEXT NOT NULL,
|
||||
tone TEXT NOT NULL,
|
||||
icon TEXT NOT NULL,
|
||||
permissions_json JSONB NOT NULL,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE users ADD COLUMN role_id TEXT;
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT users_role_id_fkey
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id)
|
||||
ON DELETE SET NULL
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX users_role_id_idx ON users(role_id);
|
||||
15
prisma/migrations/0006_user_invites/migration.sql
Normal file
15
prisma/migrations/0006_user_invites/migration.sql
Normal file
@ -0,0 +1,15 @@
|
||||
ALTER TYPE "UserStatus" ADD VALUE IF NOT EXISTS 'invited';
|
||||
|
||||
ALTER TABLE "users"
|
||||
ALTER COLUMN "password_hash" DROP NOT NULL,
|
||||
ALTER COLUMN "status" SET DEFAULT 'invited',
|
||||
ADD COLUMN IF NOT EXISTS "invite_token_hash" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "invite_token_expires_at" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "email_verified_at" TIMESTAMP(3),
|
||||
ADD COLUMN IF NOT EXISTS "last_login_at" TIMESTAMP(3);
|
||||
|
||||
UPDATE "users"
|
||||
SET "status" = 'active'
|
||||
WHERE "status" IS NULL OR "status"::text = 'active';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "users_invite_token_hash_idx" ON "users"("invite_token_hash");
|
||||
47
prisma/migrations/0007_campaigns/migration.sql
Normal file
47
prisma/migrations/0007_campaigns/migration.sql
Normal file
@ -0,0 +1,47 @@
|
||||
CREATE TABLE campaigns (
|
||||
id TEXT PRIMARY KEY,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
audience_label TEXT NOT NULL,
|
||||
audience_group TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
total_recipients INTEGER NOT NULL DEFAULT 0,
|
||||
delivered_count INTEGER NOT NULL DEFAULT 0,
|
||||
read_count INTEGER NOT NULL DEFAULT 0,
|
||||
failed_count INTEGER NOT NULL DEFAULT 0,
|
||||
delivery_rate DOUBLE PRECISION,
|
||||
read_rate DOUBLE PRECISION,
|
||||
sent_at TIMESTAMP(3),
|
||||
scheduled_at TIMESTAMP(3),
|
||||
template_name TEXT,
|
||||
language TEXT,
|
||||
message_title TEXT,
|
||||
message_body TEXT,
|
||||
primary_button TEXT,
|
||||
secondary_button TEXT,
|
||||
banner_image_url TEXT,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE campaign_recipients (
|
||||
id TEXT PRIMARY KEY,
|
||||
campaign_id TEXT NOT NULL,
|
||||
phone_number TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
sent_at TIMESTAMP(3),
|
||||
error_reason TEXT,
|
||||
device_os TEXT,
|
||||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT campaign_recipients_campaign_id_fkey
|
||||
FOREIGN KEY (campaign_id) REFERENCES campaigns(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX campaign_recipients_campaign_id_status_idx
|
||||
ON campaign_recipients(campaign_id, status);
|
||||
|
||||
CREATE INDEX campaign_recipients_campaign_id_sent_at_idx
|
||||
ON campaign_recipients(campaign_id, sent_at);
|
||||
4
prisma/migrations/0008_auth_sessions/migration.sql
Normal file
4
prisma/migrations/0008_auth_sessions/migration.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE "users"
|
||||
ADD COLUMN "refresh_token_hash" TEXT,
|
||||
ADD COLUMN "refresh_token_expires_at" TIMESTAMP(3),
|
||||
ADD COLUMN "session_version" INTEGER NOT NULL DEFAULT 0;
|
||||
@ -0,0 +1,5 @@
|
||||
ALTER TABLE "users"
|
||||
ADD COLUMN IF NOT EXISTS "password_reset_token_hash" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "password_reset_token_expires_at" TIMESTAMP(3);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "users_password_reset_token_hash_idx" ON "users"("password_reset_token_hash");
|
||||
5
prisma/migrations/0010_two_factor_auth/migration.sql
Normal file
5
prisma/migrations/0010_two_factor_auth/migration.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE "users"
|
||||
ADD COLUMN IF NOT EXISTS "two_factor_enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS "two_factor_secret_encrypted" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "two_factor_pending_secret_encrypted" TEXT,
|
||||
ADD COLUMN IF NOT EXISTS "two_factor_confirmed_at" TIMESTAMP(3);
|
||||
@ -0,0 +1,2 @@
|
||||
ALTER TABLE "users"
|
||||
ADD COLUMN "two_factor_recovery_codes_hash_json" JSONB;
|
||||
23
prisma/migrations/0012_conversation_messages/migration.sql
Normal file
23
prisma/migrations/0012_conversation_messages/migration.sql
Normal file
@ -0,0 +1,23 @@
|
||||
CREATE TABLE "conversation_messages" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"contact_id" UUID NOT NULL,
|
||||
"direction" TEXT NOT NULL,
|
||||
"message_type" TEXT NOT NULL DEFAULT 'text',
|
||||
"source" TEXT NOT NULL DEFAULT 'agent',
|
||||
"body" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'sent',
|
||||
"sender_user_id" UUID,
|
||||
"sender_name" TEXT,
|
||||
"external_message_id" TEXT,
|
||||
"webhook_event_id" TEXT,
|
||||
"occurred_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"read_at" TIMESTAMP(3),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "conversation_messages_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "conversation_messages_contact_id_fkey" FOREIGN KEY ("contact_id") REFERENCES "contacts"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX "conversation_messages_contact_id_occurred_at_idx" ON "conversation_messages"("contact_id", "occurred_at");
|
||||
CREATE UNIQUE INDEX "conversation_messages_webhook_event_id_key" ON "conversation_messages"("webhook_event_id");
|
||||
4
prisma/migrations/0013_contact_assignment/migration.sql
Normal file
4
prisma/migrations/0013_contact_assignment/migration.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE "contacts"
|
||||
ADD COLUMN "assigned_user_id" UUID,
|
||||
ADD COLUMN "assigned_user_name" TEXT,
|
||||
ADD COLUMN "assigned_at" TIMESTAMP(3);
|
||||
16
prisma/migrations/0014_message_templates/migration.sql
Normal file
16
prisma/migrations/0014_message_templates/migration.sql
Normal file
@ -0,0 +1,16 @@
|
||||
CREATE TABLE "message_templates" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"name" text NOT NULL UNIQUE,
|
||||
"category" text NOT NULL,
|
||||
"status" text NOT NULL,
|
||||
"language" text NOT NULL,
|
||||
"header_text" text,
|
||||
"body_text" text NOT NULL,
|
||||
"footer_text" text,
|
||||
"buttons_json" jsonb,
|
||||
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||||
"updated_at" timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX "message_templates_status_category_language_idx"
|
||||
ON "message_templates" ("status", "category", "language");
|
||||
233
prisma/schema.prisma
Normal file
233
prisma/schema.prisma
Normal file
@ -0,0 +1,233 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
invited
|
||||
active
|
||||
inactive
|
||||
suspended
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
email String @unique
|
||||
passwordHash String? @map("password_hash")
|
||||
refreshTokenHash String? @map("refresh_token_hash")
|
||||
refreshTokenExpiresAt DateTime? @map("refresh_token_expires_at")
|
||||
sessionVersion Int @default(0) @map("session_version")
|
||||
status UserStatus @default(invited)
|
||||
roleId String? @map("role_id")
|
||||
inviteTokenHash String? @map("invite_token_hash")
|
||||
inviteTokenExpiresAt DateTime? @map("invite_token_expires_at")
|
||||
passwordResetTokenHash String? @map("password_reset_token_hash")
|
||||
passwordResetTokenExpiresAt DateTime? @map("password_reset_token_expires_at")
|
||||
twoFactorEnabled Boolean @default(false) @map("two_factor_enabled")
|
||||
twoFactorSecretEncrypted String? @map("two_factor_secret_encrypted")
|
||||
twoFactorPendingSecretEncrypted String? @map("two_factor_pending_secret_encrypted")
|
||||
twoFactorRecoveryCodesHashJson Json? @map("two_factor_recovery_codes_hash_json")
|
||||
twoFactorConfirmedAt DateTime? @map("two_factor_confirmed_at")
|
||||
emailVerifiedAt DateTime? @map("email_verified_at")
|
||||
lastLoginAt DateTime? @map("last_login_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
role Role? @relation(fields: [roleId], references: [id])
|
||||
|
||||
@@index([passwordResetTokenHash])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(uuid())
|
||||
key String @unique
|
||||
name String
|
||||
summary String
|
||||
badge String
|
||||
tone String
|
||||
icon String
|
||||
permissionsJson Json @map("permissions_json")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
users User[]
|
||||
|
||||
@@map("roles")
|
||||
}
|
||||
|
||||
model Contact {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
phoneNumber String @unique @map("phone_number")
|
||||
email String?
|
||||
company String?
|
||||
notes String?
|
||||
isBlacklisted Boolean @default(false) @map("is_blacklisted")
|
||||
assignedUserId String? @map("assigned_user_id")
|
||||
assignedUserName String? @map("assigned_user_name")
|
||||
assignedAt DateTime? @map("assigned_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
conversationMessages ConversationMessage[]
|
||||
|
||||
@@map("contacts")
|
||||
}
|
||||
|
||||
model ConversationMessage {
|
||||
id String @id @default(uuid())
|
||||
contactId String @map("contact_id")
|
||||
direction String
|
||||
messageType String @default("text") @map("message_type")
|
||||
source String @default("agent")
|
||||
body String
|
||||
status String @default("sent")
|
||||
senderUserId String? @map("sender_user_id")
|
||||
senderName String? @map("sender_name")
|
||||
externalMessageId String? @map("external_message_id")
|
||||
webhookEventId String? @unique @map("webhook_event_id")
|
||||
occurredAt DateTime @default(now()) @map("occurred_at")
|
||||
readAt DateTime? @map("read_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([contactId, occurredAt])
|
||||
@@map("conversation_messages")
|
||||
}
|
||||
|
||||
model WebhookEvent {
|
||||
id String @id @default(uuid())
|
||||
provider String
|
||||
eventId String @unique @map("event_id")
|
||||
eventType String @map("event_type")
|
||||
senderPhone String? @map("sender_phone")
|
||||
recipientPhone String? @map("recipient_phone")
|
||||
externalMessageId String? @map("external_message_id")
|
||||
eventTimestamp DateTime @map("event_timestamp")
|
||||
payloadJson Json @map("payload_json")
|
||||
verified Boolean @default(false)
|
||||
processingStatus String @default("received") @map("processing_status")
|
||||
processingNotes String? @map("processing_notes")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("webhook_events")
|
||||
}
|
||||
|
||||
model IntegrationConfig {
|
||||
id String @id @default(uuid())
|
||||
configKey String @unique @map("config_key")
|
||||
provider String
|
||||
isEnabled Boolean @default(true) @map("is_enabled")
|
||||
configJson Json @map("config_json")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("integration_configs")
|
||||
}
|
||||
|
||||
model Job {
|
||||
id String @id @default(uuid())
|
||||
queueName String @map("queue_name")
|
||||
jobType String @map("job_type")
|
||||
status String @default("queued")
|
||||
payloadJson Json @map("payload_json")
|
||||
attempts Int @default(0)
|
||||
maxAttempts Int @default(3) @map("max_attempts")
|
||||
availableAt DateTime @default(now()) @map("available_at")
|
||||
processedAt DateTime? @map("processed_at")
|
||||
failedAt DateTime? @map("failed_at")
|
||||
errorMessage String? @map("error_message")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([queueName, status, availableAt])
|
||||
@@map("jobs")
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(uuid())
|
||||
actorUserId String? @map("actor_user_id")
|
||||
actorName String @map("actor_name")
|
||||
actorEmail String? @map("actor_email")
|
||||
actionType String @map("action_type")
|
||||
module String
|
||||
ipAddress String? @map("ip_address")
|
||||
severity String @default("default")
|
||||
details String
|
||||
metadataJson Json? @map("metadata_json")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([actorName])
|
||||
@@index([actionType])
|
||||
@@index([module])
|
||||
@@map("audit_logs")
|
||||
}
|
||||
|
||||
model MessageTemplate {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
category String
|
||||
status String
|
||||
language String
|
||||
headerText String? @map("header_text")
|
||||
bodyText String @map("body_text")
|
||||
footerText String? @map("footer_text")
|
||||
buttonsJson Json? @map("buttons_json")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([status, category, language])
|
||||
@@map("message_templates")
|
||||
}
|
||||
|
||||
model Campaign {
|
||||
id String @id @default(uuid())
|
||||
code String @unique
|
||||
name String
|
||||
audienceLabel String @map("audience_label")
|
||||
audienceGroup String @map("audience_group")
|
||||
status String
|
||||
totalRecipients Int @default(0) @map("total_recipients")
|
||||
deliveredCount Int @default(0) @map("delivered_count")
|
||||
readCount Int @default(0) @map("read_count")
|
||||
failedCount Int @default(0) @map("failed_count")
|
||||
deliveryRate Float? @map("delivery_rate")
|
||||
readRate Float? @map("read_rate")
|
||||
sentAt DateTime? @map("sent_at")
|
||||
scheduledAt DateTime? @map("scheduled_at")
|
||||
templateName String? @map("template_name")
|
||||
language String?
|
||||
messageTitle String? @map("message_title")
|
||||
messageBody String? @map("message_body")
|
||||
primaryButton String? @map("primary_button")
|
||||
secondaryButton String? @map("secondary_button")
|
||||
bannerImageUrl String? @map("banner_image_url")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
recipients CampaignRecipient[]
|
||||
|
||||
@@map("campaigns")
|
||||
}
|
||||
|
||||
model CampaignRecipient {
|
||||
id String @id @default(uuid())
|
||||
campaignId String @map("campaign_id")
|
||||
phoneNumber String @map("phone_number")
|
||||
status String
|
||||
sentAt DateTime? @map("sent_at")
|
||||
errorReason String? @map("error_reason")
|
||||
deviceOs String? @map("device_os")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([campaignId, status])
|
||||
@@index([campaignId, sentAt])
|
||||
@@map("campaign_recipients")
|
||||
}
|
||||
70
prisma/scripts/baseline-legacy-db.mjs
Normal file
70
prisma/scripts/baseline-legacy-db.mjs
Normal file
@ -0,0 +1,70 @@
|
||||
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const repoRoot = resolve(new URL('../..', import.meta.url).pathname);
|
||||
const migrationsDir = resolve(repoRoot, 'prisma/migrations');
|
||||
const schemaPath = resolve(repoRoot, 'prisma/schema.prisma');
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
const prismaCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
|
||||
for (const envPath of ['.env', '.env.local', 'backend/.env', 'backend/.env.local']) {
|
||||
const absolutePath = resolve(repoRoot, envPath);
|
||||
if (!existsSync(absolutePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = readFileSync(absolutePath, 'utf8');
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const separatorIndex = trimmed.indexOf('=');
|
||||
if (separatorIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = trimmed.slice(0, separatorIndex).trim();
|
||||
const value = trimmed.slice(separatorIndex + 1).trim();
|
||||
if (key && !(key in process.env)) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const migrations = readdirSync(migrationsDir)
|
||||
.filter((name) => /^\d+_/.test(name))
|
||||
.filter((name) => statSync(resolve(migrationsDir, name)).isDirectory())
|
||||
.sort((left, right) => left.localeCompare(right));
|
||||
|
||||
if (migrations.length === 0) {
|
||||
console.error('No Prisma migrations found.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Found ${migrations.length} migrations.`);
|
||||
if (dryRun) {
|
||||
migrations.forEach((migration) => console.log(`- ${migration}`));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
for (const migration of migrations) {
|
||||
console.log(`Marking migration as applied: ${migration}`);
|
||||
const result = spawnSync(
|
||||
prismaCommand,
|
||||
['prisma', 'migrate', 'resolve', '--applied', migration, '--schema', schemaPath],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Legacy Prisma baseline completed.');
|
||||
Reference in New Issue
Block a user