agrs-sequelize-sdk 1.4.17 → 1.4.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/2026-05-17-PIXELS-FOLLOWUP.md +85 -0
- package/migrations/2026-05-17-add-platform-to-ad.js +19 -0
- package/migrations/2026-05-17-add-platform-to-adaccount.js +19 -0
- package/migrations/2026-05-17-add-platform-to-adperformance.js +19 -0
- package/migrations/2026-05-17-add-platform-to-adset.js +19 -0
- package/migrations/2026-05-17-add-platform-to-adsetperformance.js +19 -0
- package/migrations/2026-05-17-add-platform-to-aiarticleretryqueue.js +19 -0
- package/migrations/2026-05-17-add-platform-to-aicampaignqueue.js +19 -0
- package/migrations/2026-05-17-add-platform-to-article.js +19 -0
- package/migrations/2026-05-17-add-platform-to-campaign.js +19 -0
- package/migrations/2026-05-17-add-platform-to-codefuelcampaign.js +19 -0
- package/migrations/2026-05-17-add-platform-to-dynamicfeed.js +19 -0
- package/migrations/2026-05-17-add-platform-to-facebookretryqueue.js +19 -0
- package/migrations/2026-05-17-add-platform-to-pages.js +19 -0
- package/migrations/2026-05-17-add-platform-to-presets.js +19 -0
- package/migrations/RUN_ME_MANUALLY_2026-05-17.sql +99 -0
- package/migrations/add-requested-from-dashboard-to-articles.js +17 -17
- package/migrations/change-adset-name-to-text.js +79 -79
- package/models/AICampaignQueue.js +143 -136
- package/models/AIGenerationLog.js +85 -85
- package/models/AIGenerationRequest.js +212 -212
- package/models/Ad.js +6 -0
- package/models/AdAccount.js +6 -0
- package/models/AdAccountValues.js +25 -25
- package/models/AdHistory.js +30 -30
- package/models/AdPerformance.js +100 -94
- package/models/AdSet.js +295 -289
- package/models/AdSetHistory.js +30 -30
- package/models/AdsetPerformance.js +132 -126
- package/models/AiArticleRetryQueue.js +157 -150
- package/models/AiCreationRetryQueue.js +127 -127
- package/models/ApiAuditLog.js +84 -0
- package/models/ApiToken.js +67 -0
- package/models/Article.js +212 -206
- package/models/AutomationRule.js +173 -173
- package/models/BannerTemplate.js +129 -129
- package/models/Buyers.js +25 -25
- package/models/Campaign.js +163 -157
- package/models/CampaignActionHistory.js +86 -86
- package/models/CampaignCreationLog.js +309 -309
- package/models/CampaignCreationLogV2.js +314 -314
- package/models/CampaignHistory.js +33 -33
- package/models/Channel.js +55 -55
- package/models/CodefuelCampaign.js +6 -0
- package/models/Domain.js +39 -39
- package/models/DynamicFeed.js +218 -212
- package/models/ExplorAdsChannel.js +61 -61
- package/models/FacebookRetryQueue.js +163 -156
- package/models/Feed.js +33 -33
- package/models/FeedArticleConfiguration.js +80 -80
- package/models/FrontStoryChannel.js +59 -59
- package/models/FrontStoryChannelV2.js +60 -60
- package/models/GenericFlowRequest.js +114 -114
- package/models/MidoWebChannel.js +47 -47
- package/models/MineChannel.js +42 -42
- package/models/Pages.js +105 -99
- package/models/PipelineExecution.js +59 -59
- package/models/PolicyDogsCreativeCache.js +50 -50
- package/models/PolicyDogsImageCache.js +30 -30
- package/models/Presets.js +40 -34
- package/models/RSOCFeedCampaign.js +375 -375
- package/models/RsocKeywordPerformance.js +110 -110
- package/models/RuleAction.js +90 -90
- package/models/RuleCondition.js +137 -137
- package/models/RuleExecution.js +107 -107
- package/models/RulesValues.js +56 -56
- package/models/SupportedLocale.js +23 -23
- package/models/SyncHistory.js +249 -249
- package/models/TTQChannel.js +42 -42
- package/models/TemplateMetadata.js +260 -260
- package/models/Tier2_AdAccounts.js +110 -110
- package/models/Tier2_Assets.js +70 -70
- package/models/Tier2_BusinessManagers.js +105 -105
- package/models/Tier2_CreditLines.js +99 -99
- package/models/Tier2_Pages.js +91 -91
- package/models/Tier2_Pixels.js +82 -82
- package/models/Tier2_Tokens.js +64 -64
- package/models/Tier2_UserAdAccounts.js +83 -83
- package/models/TokenRotationState.js +121 -121
- package/models/TonicRSOCKeywordPerformance.js +122 -122
- package/models/Users.js +148 -148
- package/models/Vertical.js +25 -25
- package/models/newFiles.js +137 -137
- package/package.json +19 -21
- package/run.sh +214 -214
- package/services/sequelizeService.js +110 -110
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Platform column normalization — follow-on work
|
|
2
|
+
|
|
3
|
+
## Tables skipped from the 2026-05-17 platform rollout
|
|
4
|
+
|
|
5
|
+
The following 4 tables were intentionally OMITTED from the 2026-05-17 platform-column
|
|
6
|
+
rollout because each already has a `platform` column of type VARCHAR with
|
|
7
|
+
capitalized values (e.g. "Facebook"). Adding an ENUM column with the same name
|
|
8
|
+
would either fail outright or destructively override existing data.
|
|
9
|
+
|
|
10
|
+
| Table | Existing column type | Existing values seen |
|
|
11
|
+
|--------------------------------|----------------------|--------------------------------------------------------|
|
|
12
|
+
| `Pixels` | varchar | "Facebook", "Tiktok", "Taboola", "Outbrain", "Yahoo" |
|
|
13
|
+
| `CampaignCreationLogs` | varchar(50) | "Facebook", "fb", NULL |
|
|
14
|
+
| `CampaignCreationLogsV2` | varchar(50) | "Facebook", "fb", NULL |
|
|
15
|
+
| `rsoc_feed_campaigns` | varchar | "Facebook", "fb" |
|
|
16
|
+
|
|
17
|
+
The rest of the rollout uses lowercase short codes: `fb`, `tt`, `snp`.
|
|
18
|
+
|
|
19
|
+
## Why we did not "just add another column"
|
|
20
|
+
|
|
21
|
+
A naive `addColumn("platform")` collides with the existing column name. A naive
|
|
22
|
+
`UPDATE` to lowercase the values would touch production data and is destructive.
|
|
23
|
+
Both violate the safety constraint of this rollout (additive only).
|
|
24
|
+
|
|
25
|
+
## Recommended path (separate plan)
|
|
26
|
+
|
|
27
|
+
For each of the 4 tables:
|
|
28
|
+
|
|
29
|
+
1. Add a new column `platform_code` ENUM('fb','tt','snp','tab','out','yh') NOT NULL DEFAULT 'fb'
|
|
30
|
+
2. Backfill in a single transaction. Example for Pixels:
|
|
31
|
+
```sql
|
|
32
|
+
UPDATE "Pixels" SET platform_code = CASE platform
|
|
33
|
+
WHEN 'Facebook' THEN 'fb'
|
|
34
|
+
WHEN 'Tiktok' THEN 'tt'
|
|
35
|
+
WHEN 'Taboola' THEN 'tab'
|
|
36
|
+
WHEN 'Outbrain' THEN 'out'
|
|
37
|
+
WHEN 'Yahoo' THEN 'yh'
|
|
38
|
+
ELSE 'fb'
|
|
39
|
+
END;
|
|
40
|
+
```
|
|
41
|
+
For CampaignCreationLogs / CampaignCreationLogsV2 / rsoc_feed_campaigns the
|
|
42
|
+
backfill is simpler:
|
|
43
|
+
```sql
|
|
44
|
+
UPDATE "CampaignCreationLogs" SET platform_code = CASE LOWER(COALESCE(platform, 'fb'))
|
|
45
|
+
WHEN 'facebook' THEN 'fb'
|
|
46
|
+
WHEN 'fb' THEN 'fb'
|
|
47
|
+
WHEN 'tiktok' THEN 'tt'
|
|
48
|
+
WHEN 'tt' THEN 'tt'
|
|
49
|
+
ELSE 'fb'
|
|
50
|
+
END;
|
|
51
|
+
```
|
|
52
|
+
3. Update server code to read `platform_code` for new logic; leave the legacy
|
|
53
|
+
`platform` column untouched for backward compat.
|
|
54
|
+
4. Eventually deprecate the legacy `platform` column and rename `platform_code`
|
|
55
|
+
→ `platform` once all callers have migrated. That happens in a separate,
|
|
56
|
+
later cleanup plan.
|
|
57
|
+
|
|
58
|
+
## Until then
|
|
59
|
+
|
|
60
|
+
The dashboard's platform adapter (created in the Phase 0 backend plan) MUST
|
|
61
|
+
normalize legacy `platform` values when comparing to the canonical fb/tt/snp
|
|
62
|
+
codes. Example normalizer:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
function normalizeLegacyPlatform(legacy) {
|
|
66
|
+
if (!legacy) return "fb";
|
|
67
|
+
const map = {
|
|
68
|
+
Facebook: "fb",
|
|
69
|
+
facebook: "fb",
|
|
70
|
+
FB: "fb",
|
|
71
|
+
fb: "fb",
|
|
72
|
+
Tiktok: "tt",
|
|
73
|
+
TikTok: "tt",
|
|
74
|
+
tiktok: "tt",
|
|
75
|
+
tt: "tt",
|
|
76
|
+
Snapchat: "snp",
|
|
77
|
+
snapchat: "snp",
|
|
78
|
+
snp: "snp",
|
|
79
|
+
Taboola: "tab",
|
|
80
|
+
Outbrain: "out",
|
|
81
|
+
Yahoo: "yh",
|
|
82
|
+
};
|
|
83
|
+
return map[legacy] || "fb";
|
|
84
|
+
}
|
|
85
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("Ad", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("Ad", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_Ad_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("AdAccount", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("AdAccount", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_AdAccount_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("AdPerformance", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("AdPerformance", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_AdPerformance_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("AdSet", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("AdSet", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_AdSet_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("AdSetPerformance", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("AdSetPerformance", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_AdSetPerformance_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("ai_article_retry_queue", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("ai_article_retry_queue", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_ai_article_retry_queue_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("ai_campaign_queue", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("ai_campaign_queue", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_ai_campaign_queue_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("articles", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("articles", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_articles_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("Campaign", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("Campaign", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_Campaign_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("CodefuelCampaigns", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("CodefuelCampaigns", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_CodefuelCampaigns_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("dynamic_feeds", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("dynamic_feeds", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_dynamic_feeds_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("facebook_retry_queue", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("facebook_retry_queue", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_facebook_retry_queue_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("Pages", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("Pages", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_Pages_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("Presets", "platform", {
|
|
6
|
+
type: Sequelize.ENUM("fb", "tt", "snp"),
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: "fb",
|
|
9
|
+
comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
|
|
10
|
+
});
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
down: async (queryInterface, Sequelize) => {
|
|
14
|
+
await queryInterface.removeColumn("Presets", "platform");
|
|
15
|
+
await queryInterface.sequelize.query(
|
|
16
|
+
'DROP TYPE IF EXISTS "enum_Presets_platform";'
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
-- Platform column rollout — 14 tables (additive).
|
|
2
|
+
-- Generated 2026-05-17. Run against staging first, verify, then production.
|
|
3
|
+
-- All ops are additive. No data loss possible. Backfill happens automatically via DEFAULT 'fb'.
|
|
4
|
+
--
|
|
5
|
+
-- IMPORTANT — 4 tables intentionally OMITTED from this batch because they
|
|
6
|
+
-- already have a `platform` column of type VARCHAR with capitalized values
|
|
7
|
+
-- (e.g. "Facebook"). Adding an ENUM column with the same name would conflict.
|
|
8
|
+
-- These require a separate normalization plan (see migrations/2026-05-17-PIXELS-FOLLOWUP.md):
|
|
9
|
+
-- - CampaignCreationLogs (column platform varchar; values 'Facebook' / 'fb' / NULL)
|
|
10
|
+
-- - CampaignCreationLogsV2 (column platform varchar; values 'Facebook' / 'fb' / NULL)
|
|
11
|
+
-- - rsoc_feed_campaigns (column platform varchar; values 'Facebook' / 'fb')
|
|
12
|
+
-- - Pixels (column platform varchar; values 'Facebook' / 'Tiktok' / 'Yahoo' / etc.)
|
|
13
|
+
--
|
|
14
|
+
-- Tables included in this rollout (14):
|
|
15
|
+
-- 1 Campaign 6 ai_campaign_queue 11 articles
|
|
16
|
+
-- 2 AdSet 7 facebook_retry_queue 12 dynamic_feeds
|
|
17
|
+
-- 3 Ad 8 ai_article_retry_queue 13 CodefuelCampaigns
|
|
18
|
+
-- 4 AdAccount 9 AdSetPerformance 14 Presets
|
|
19
|
+
-- 5 Pages 10 AdPerformance
|
|
20
|
+
|
|
21
|
+
BEGIN;
|
|
22
|
+
|
|
23
|
+
DO $$
|
|
24
|
+
DECLARE
|
|
25
|
+
tbl text;
|
|
26
|
+
BEGIN
|
|
27
|
+
FOR tbl IN
|
|
28
|
+
SELECT unnest(ARRAY[
|
|
29
|
+
'Campaign', 'AdSet', 'Ad', 'AdAccount', 'Pages',
|
|
30
|
+
'ai_campaign_queue', 'facebook_retry_queue', 'ai_article_retry_queue',
|
|
31
|
+
'AdSetPerformance', 'AdPerformance',
|
|
32
|
+
'articles', 'dynamic_feeds', 'CodefuelCampaigns',
|
|
33
|
+
'Presets'
|
|
34
|
+
])
|
|
35
|
+
LOOP
|
|
36
|
+
-- Create the per-table ENUM type if it doesn't already exist
|
|
37
|
+
EXECUTE format(
|
|
38
|
+
'DO $inner$ BEGIN
|
|
39
|
+
CREATE TYPE %I AS ENUM (''fb'', ''tt'', ''snp'');
|
|
40
|
+
EXCEPTION
|
|
41
|
+
WHEN duplicate_object THEN NULL;
|
|
42
|
+
END $inner$;',
|
|
43
|
+
'enum_' || tbl || '_platform'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Add the column if it doesn't exist
|
|
47
|
+
EXECUTE format(
|
|
48
|
+
'ALTER TABLE %I ADD COLUMN IF NOT EXISTS platform %I NOT NULL DEFAULT ''fb'';',
|
|
49
|
+
tbl,
|
|
50
|
+
'enum_' || tbl || '_platform'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
-- Comment
|
|
54
|
+
EXECUTE format(
|
|
55
|
+
'COMMENT ON COLUMN %I.platform IS ''Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat'';',
|
|
56
|
+
tbl
|
|
57
|
+
);
|
|
58
|
+
END LOOP;
|
|
59
|
+
END $$;
|
|
60
|
+
|
|
61
|
+
COMMIT;
|
|
62
|
+
|
|
63
|
+
-- Verification queries — run after COMMIT to confirm:
|
|
64
|
+
-- 1) Confirm 14 ENUM columns added (plus 4 pre-existing varchar columns = 18 total rows expected):
|
|
65
|
+
-- SELECT table_name, column_name, data_type, udt_name, column_default
|
|
66
|
+
-- FROM information_schema.columns
|
|
67
|
+
-- WHERE column_name = 'platform'
|
|
68
|
+
-- ORDER BY table_name;
|
|
69
|
+
--
|
|
70
|
+
-- 2) Confirm backfill: every row should have platform='fb':
|
|
71
|
+
-- SELECT 'Campaign' AS tbl, platform, COUNT(*) FROM "Campaign" GROUP BY platform
|
|
72
|
+
-- UNION ALL SELECT 'AdSet', platform, COUNT(*) FROM "AdSet" GROUP BY platform
|
|
73
|
+
-- UNION ALL SELECT 'Ad', platform, COUNT(*) FROM "Ad" GROUP BY platform
|
|
74
|
+
-- UNION ALL SELECT 'AdAccount', platform, COUNT(*) FROM "AdAccount" GROUP BY platform
|
|
75
|
+
-- UNION ALL SELECT 'Pages', platform, COUNT(*) FROM "Pages" GROUP BY platform
|
|
76
|
+
-- UNION ALL SELECT 'ai_campaign_queue', platform, COUNT(*) FROM ai_campaign_queue GROUP BY platform
|
|
77
|
+
-- UNION ALL SELECT 'facebook_retry_queue', platform, COUNT(*) FROM facebook_retry_queue GROUP BY platform
|
|
78
|
+
-- UNION ALL SELECT 'ai_article_retry_queue',platform, COUNT(*) FROM ai_article_retry_queue GROUP BY platform
|
|
79
|
+
-- UNION ALL SELECT 'AdSetPerformance', platform, COUNT(*) FROM "AdSetPerformance" GROUP BY platform
|
|
80
|
+
-- UNION ALL SELECT 'AdPerformance', platform, COUNT(*) FROM "AdPerformance" GROUP BY platform
|
|
81
|
+
-- UNION ALL SELECT 'articles', platform, COUNT(*) FROM articles GROUP BY platform
|
|
82
|
+
-- UNION ALL SELECT 'dynamic_feeds', platform, COUNT(*) FROM dynamic_feeds GROUP BY platform
|
|
83
|
+
-- UNION ALL SELECT 'CodefuelCampaigns',platform, COUNT(*) FROM "CodefuelCampaigns" GROUP BY platform
|
|
84
|
+
-- UNION ALL SELECT 'Presets', platform, COUNT(*) FROM "Presets" GROUP BY platform
|
|
85
|
+
-- ORDER BY tbl, platform;
|
|
86
|
+
|
|
87
|
+
-- ROLLBACK plan (separate transaction; only if needed):
|
|
88
|
+
-- BEGIN;
|
|
89
|
+
-- DO $$ DECLARE tbl text; BEGIN
|
|
90
|
+
-- FOR tbl IN SELECT unnest(ARRAY['Campaign','AdSet','Ad','AdAccount','Pages',
|
|
91
|
+
-- 'ai_campaign_queue','facebook_retry_queue','ai_article_retry_queue',
|
|
92
|
+
-- 'AdSetPerformance','AdPerformance',
|
|
93
|
+
-- 'articles','dynamic_feeds','CodefuelCampaigns','Presets'])
|
|
94
|
+
-- LOOP
|
|
95
|
+
-- EXECUTE format('ALTER TABLE %I DROP COLUMN IF EXISTS platform;', tbl);
|
|
96
|
+
-- EXECUTE format('DROP TYPE IF EXISTS %I;', 'enum_' || tbl || '_platform');
|
|
97
|
+
-- END LOOP;
|
|
98
|
+
-- END $$;
|
|
99
|
+
-- COMMIT;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
up: async (queryInterface, Sequelize) => {
|
|
5
|
-
await queryInterface.addColumn("articles", "requestedFromDashboard", {
|
|
6
|
-
type: Sequelize.BOOLEAN,
|
|
7
|
-
allowNull: false,
|
|
8
|
-
defaultValue: false,
|
|
9
|
-
comment:
|
|
10
|
-
"Indicates if the article was created through our dashboard (true) or imported from external sources (false)",
|
|
11
|
-
});
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
down: async (queryInterface, Sequelize) => {
|
|
15
|
-
await queryInterface.removeColumn("articles", "requestedFromDashboard");
|
|
16
|
-
},
|
|
17
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
await queryInterface.addColumn("articles", "requestedFromDashboard", {
|
|
6
|
+
type: Sequelize.BOOLEAN,
|
|
7
|
+
allowNull: false,
|
|
8
|
+
defaultValue: false,
|
|
9
|
+
comment:
|
|
10
|
+
"Indicates if the article was created through our dashboard (true) or imported from external sources (false)",
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
down: async (queryInterface, Sequelize) => {
|
|
15
|
+
await queryInterface.removeColumn("articles", "requestedFromDashboard");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
up: async (queryInterface, Sequelize) => {
|
|
5
|
-
// Change AdSetName column from VARCHAR(255) to TEXT
|
|
6
|
-
// This is a SAFE operation - PostgreSQL can convert VARCHAR to TEXT without data loss
|
|
7
|
-
// All existing data will be preserved
|
|
8
|
-
|
|
9
|
-
// First, check if column exists and get its current type
|
|
10
|
-
const [columns] = await queryInterface.sequelize.query(`
|
|
11
|
-
SELECT column_name, data_type, character_maximum_length
|
|
12
|
-
FROM information_schema.columns
|
|
13
|
-
WHERE table_name = 'AdSet' AND column_name = 'AdSetName'
|
|
14
|
-
`);
|
|
15
|
-
|
|
16
|
-
if (columns.length === 0) {
|
|
17
|
-
console.log("⚠️ AdSetName column not found, skipping migration");
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const currentType = columns[0].data_type;
|
|
22
|
-
const currentMaxLength = columns[0].character_maximum_length;
|
|
23
|
-
|
|
24
|
-
console.log(
|
|
25
|
-
`Current AdSetName type: ${currentType}(${
|
|
26
|
-
currentMaxLength || "unlimited"
|
|
27
|
-
})`
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
// Only change if it's currently VARCHAR with length limit
|
|
31
|
-
if (currentType === "character varying" && currentMaxLength) {
|
|
32
|
-
console.log("Converting AdSetName from VARCHAR to TEXT...");
|
|
33
|
-
|
|
34
|
-
// Use direct SQL for more control
|
|
35
|
-
await queryInterface.sequelize.query(`
|
|
36
|
-
ALTER TABLE "AdSet"
|
|
37
|
-
ALTER COLUMN "AdSetName" TYPE TEXT
|
|
38
|
-
USING "AdSetName"::TEXT
|
|
39
|
-
`);
|
|
40
|
-
|
|
41
|
-
console.log("✅ AdSetName successfully converted to TEXT");
|
|
42
|
-
} else if (currentType === "text") {
|
|
43
|
-
console.log("✅ AdSetName is already TEXT, no change needed");
|
|
44
|
-
} else {
|
|
45
|
-
console.log(`⚠️ AdSetName is ${currentType}, not changing`);
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
down: async (queryInterface, Sequelize) => {
|
|
50
|
-
// Revert back to VARCHAR(255)
|
|
51
|
-
// WARNING: This might truncate existing long values if any exist
|
|
52
|
-
console.log(
|
|
53
|
-
"⚠️ Reverting AdSetName to VARCHAR(255) - this may truncate long values!"
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Check for any values longer than 255 characters
|
|
57
|
-
const [longValues] = await queryInterface.sequelize.query(`
|
|
58
|
-
SELECT COUNT(*) as count
|
|
59
|
-
FROM "AdSet"
|
|
60
|
-
WHERE LENGTH("AdSetName") > 255
|
|
61
|
-
`);
|
|
62
|
-
|
|
63
|
-
if (longValues[0].count > 0) {
|
|
64
|
-
console.log(
|
|
65
|
-
`⚠️ WARNING: ${longValues[0].count} rows have AdSetName longer than 255 characters!`
|
|
66
|
-
);
|
|
67
|
-
console.log(
|
|
68
|
-
"⚠️ These will be truncated if you proceed with the down migration."
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
await queryInterface.changeColumn("AdSet", "AdSetName", {
|
|
73
|
-
type: Sequelize.STRING(255),
|
|
74
|
-
allowNull: true,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
console.log("✅ AdSetName reverted to VARCHAR(255)");
|
|
78
|
-
},
|
|
79
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
up: async (queryInterface, Sequelize) => {
|
|
5
|
+
// Change AdSetName column from VARCHAR(255) to TEXT
|
|
6
|
+
// This is a SAFE operation - PostgreSQL can convert VARCHAR to TEXT without data loss
|
|
7
|
+
// All existing data will be preserved
|
|
8
|
+
|
|
9
|
+
// First, check if column exists and get its current type
|
|
10
|
+
const [columns] = await queryInterface.sequelize.query(`
|
|
11
|
+
SELECT column_name, data_type, character_maximum_length
|
|
12
|
+
FROM information_schema.columns
|
|
13
|
+
WHERE table_name = 'AdSet' AND column_name = 'AdSetName'
|
|
14
|
+
`);
|
|
15
|
+
|
|
16
|
+
if (columns.length === 0) {
|
|
17
|
+
console.log("⚠️ AdSetName column not found, skipping migration");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const currentType = columns[0].data_type;
|
|
22
|
+
const currentMaxLength = columns[0].character_maximum_length;
|
|
23
|
+
|
|
24
|
+
console.log(
|
|
25
|
+
`Current AdSetName type: ${currentType}(${
|
|
26
|
+
currentMaxLength || "unlimited"
|
|
27
|
+
})`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Only change if it's currently VARCHAR with length limit
|
|
31
|
+
if (currentType === "character varying" && currentMaxLength) {
|
|
32
|
+
console.log("Converting AdSetName from VARCHAR to TEXT...");
|
|
33
|
+
|
|
34
|
+
// Use direct SQL for more control
|
|
35
|
+
await queryInterface.sequelize.query(`
|
|
36
|
+
ALTER TABLE "AdSet"
|
|
37
|
+
ALTER COLUMN "AdSetName" TYPE TEXT
|
|
38
|
+
USING "AdSetName"::TEXT
|
|
39
|
+
`);
|
|
40
|
+
|
|
41
|
+
console.log("✅ AdSetName successfully converted to TEXT");
|
|
42
|
+
} else if (currentType === "text") {
|
|
43
|
+
console.log("✅ AdSetName is already TEXT, no change needed");
|
|
44
|
+
} else {
|
|
45
|
+
console.log(`⚠️ AdSetName is ${currentType}, not changing`);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
down: async (queryInterface, Sequelize) => {
|
|
50
|
+
// Revert back to VARCHAR(255)
|
|
51
|
+
// WARNING: This might truncate existing long values if any exist
|
|
52
|
+
console.log(
|
|
53
|
+
"⚠️ Reverting AdSetName to VARCHAR(255) - this may truncate long values!"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Check for any values longer than 255 characters
|
|
57
|
+
const [longValues] = await queryInterface.sequelize.query(`
|
|
58
|
+
SELECT COUNT(*) as count
|
|
59
|
+
FROM "AdSet"
|
|
60
|
+
WHERE LENGTH("AdSetName") > 255
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
if (longValues[0].count > 0) {
|
|
64
|
+
console.log(
|
|
65
|
+
`⚠️ WARNING: ${longValues[0].count} rows have AdSetName longer than 255 characters!`
|
|
66
|
+
);
|
|
67
|
+
console.log(
|
|
68
|
+
"⚠️ These will be truncated if you proceed with the down migration."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await queryInterface.changeColumn("AdSet", "AdSetName", {
|
|
73
|
+
type: Sequelize.STRING(255),
|
|
74
|
+
allowNull: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
console.log("✅ AdSetName reverted to VARCHAR(255)");
|
|
78
|
+
},
|
|
79
|
+
};
|