feat(campaigns): add campaign management schema migration

This commit is contained in:
AFFAANh
2026-08-01 12:23:23 +05:30
parent 19e1e84fb7
commit 38b6118fc3
2 changed files with 181 additions and 0 deletions
+124
View File
@@ -126,6 +126,130 @@ DROP TABLE IF EXISTS maskanx_social_posts;
DROP TABLE IF EXISTS maskanx_campaigns;
DROP TABLE IF EXISTS maskanx_cron_jobs;
DROP TABLE IF EXISTS maskanx_chats;
""",
),
Migration(
version="202608010001",
name="campaign_management_schema",
up_sql="""
ALTER TABLE maskanx_campaigns
ADD COLUMN IF NOT EXISTS origin TEXT NOT NULL DEFAULT 'maskanx',
ADD COLUMN IF NOT EXISTS ad_account_id TEXT,
ADD COLUMN IF NOT EXISTS budget JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS guardrails JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS targeting JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS advanced JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS meta_campaign_id TEXT,
ADD COLUMN IF NOT EXISTS sync_status TEXT NOT NULL DEFAULT 'not_synced',
ADD COLUMN IF NOT EXISTS sync_error TEXT,
ADD COLUMN IF NOT EXISTS last_synced_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS approved_by TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS launched_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS stopped_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS deleted_in_meta_at TIMESTAMPTZ;
CREATE UNIQUE INDEX IF NOT EXISTS uq_maskanx_campaigns_meta_id
ON maskanx_campaigns (ad_account_id, meta_campaign_id)
WHERE meta_campaign_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS maskanx_campaign_adsets (
id TEXT PRIMARY KEY,
campaign_id TEXT NOT NULL
REFERENCES maskanx_campaigns(id) ON DELETE CASCADE,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
targeting JSONB NOT NULL DEFAULT '{}'::jsonb,
budget JSONB NOT NULL DEFAULT '{}'::jsonb,
optimization_goal TEXT,
billing_event TEXT,
schedule JSONB NOT NULL DEFAULT '{}'::jsonb,
meta_adset_id TEXT,
sync_status TEXT NOT NULL DEFAULT 'not_synced',
sync_error TEXT,
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_maskanx_campaign_adsets_campaign
ON maskanx_campaign_adsets (campaign_id);
CREATE TABLE IF NOT EXISTS maskanx_campaign_ads (
id TEXT PRIMARY KEY,
campaign_id TEXT NOT NULL
REFERENCES maskanx_campaigns(id) ON DELETE CASCADE,
adset_id TEXT
REFERENCES maskanx_campaign_adsets(id) ON DELETE CASCADE,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
creative JSONB NOT NULL DEFAULT '{}'::jsonb,
meta_ad_id TEXT,
meta_creative_id TEXT,
sync_status TEXT NOT NULL DEFAULT 'not_synced',
sync_error TEXT,
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_maskanx_campaign_ads_campaign
ON maskanx_campaign_ads (campaign_id);
CREATE TABLE IF NOT EXISTS maskanx_campaign_events (
id TEXT PRIMARY KEY,
campaign_id TEXT NOT NULL
REFERENCES maskanx_campaigns(id) ON DELETE CASCADE,
event_type TEXT NOT NULL,
actor TEXT,
reason TEXT,
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_maskanx_campaign_events_campaign
ON maskanx_campaign_events (campaign_id, occurred_at DESC);
CREATE TABLE IF NOT EXISTS maskanx_campaign_insights (
id TEXT PRIMARY KEY,
level TEXT NOT NULL,
object_id TEXT NOT NULL,
campaign_id TEXT
REFERENCES maskanx_campaigns(id) ON DELETE CASCADE,
date DATE NOT NULL,
breakdown_key TEXT NOT NULL DEFAULT '',
breakdown_value TEXT NOT NULL DEFAULT '',
metrics JSONB NOT NULL DEFAULT '{}'::jsonb,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_maskanx_campaign_insights
ON maskanx_campaign_insights
(level, object_id, date, breakdown_key, breakdown_value);
""",
down_sql="""
DROP TABLE IF EXISTS maskanx_campaign_insights;
DROP TABLE IF EXISTS maskanx_campaign_events;
DROP TABLE IF EXISTS maskanx_campaign_ads;
DROP TABLE IF EXISTS maskanx_campaign_adsets;
DROP INDEX IF EXISTS uq_maskanx_campaigns_meta_id;
ALTER TABLE maskanx_campaigns
DROP COLUMN IF EXISTS origin,
DROP COLUMN IF EXISTS ad_account_id,
DROP COLUMN IF EXISTS budget,
DROP COLUMN IF EXISTS guardrails,
DROP COLUMN IF EXISTS targeting,
DROP COLUMN IF EXISTS advanced,
DROP COLUMN IF EXISTS meta_campaign_id,
DROP COLUMN IF EXISTS sync_status,
DROP COLUMN IF EXISTS sync_error,
DROP COLUMN IF EXISTS last_synced_at,
DROP COLUMN IF EXISTS approved_by,
DROP COLUMN IF EXISTS approved_at,
DROP COLUMN IF EXISTS launched_at,
DROP COLUMN IF EXISTS stopped_at,
DROP COLUMN IF EXISTS deleted_in_meta_at;
""",
),
)
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""The campaign schema migration must be registered and reversible."""
from adclaw.db.migrations import MIGRATIONS
VERSION = "202608010001"
def _migration():
return next((m for m in MIGRATIONS if m.version == VERSION), None)
def test_campaign_migration_is_registered():
assert _migration() is not None
def test_campaign_migration_creates_expected_tables():
up = _migration().up_sql
for table in (
"maskanx_campaign_adsets",
"maskanx_campaign_ads",
"maskanx_campaign_events",
"maskanx_campaign_insights",
):
assert f"CREATE TABLE IF NOT EXISTS {table}" in up
def test_campaign_migration_adds_columns_to_campaigns():
up = _migration().up_sql
for column in (
"origin",
"ad_account_id",
"budget",
"guardrails",
"targeting",
"advanced",
"meta_campaign_id",
"sync_status",
"sync_error",
"last_synced_at",
"approved_by",
"approved_at",
"launched_at",
"stopped_at",
"deleted_in_meta_at",
):
assert f"ADD COLUMN IF NOT EXISTS {column}" in up
def test_campaign_migration_down_drops_new_tables():
down = _migration().down_sql
for table in (
"maskanx_campaign_insights",
"maskanx_campaign_events",
"maskanx_campaign_ads",
"maskanx_campaign_adsets",
):
assert f"DROP TABLE IF EXISTS {table}" in down