Initial BizOne portal setup

This commit is contained in:
2026-05-11 11:36:33 +07:00
commit 57017dd397
249 changed files with 41305 additions and 0 deletions

View 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()
);

View 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);

View 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);

View 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);

View 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);

View 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");

View 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);

View 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;

View File

@ -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");

View 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);

View File

@ -0,0 +1,2 @@
ALTER TABLE "users"
ADD COLUMN "two_factor_recovery_codes_hash_json" JSONB;

View 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");

View 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);

View 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");