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.
Files changed (86) hide show
  1. package/migrations/2026-05-17-PIXELS-FOLLOWUP.md +85 -0
  2. package/migrations/2026-05-17-add-platform-to-ad.js +19 -0
  3. package/migrations/2026-05-17-add-platform-to-adaccount.js +19 -0
  4. package/migrations/2026-05-17-add-platform-to-adperformance.js +19 -0
  5. package/migrations/2026-05-17-add-platform-to-adset.js +19 -0
  6. package/migrations/2026-05-17-add-platform-to-adsetperformance.js +19 -0
  7. package/migrations/2026-05-17-add-platform-to-aiarticleretryqueue.js +19 -0
  8. package/migrations/2026-05-17-add-platform-to-aicampaignqueue.js +19 -0
  9. package/migrations/2026-05-17-add-platform-to-article.js +19 -0
  10. package/migrations/2026-05-17-add-platform-to-campaign.js +19 -0
  11. package/migrations/2026-05-17-add-platform-to-codefuelcampaign.js +19 -0
  12. package/migrations/2026-05-17-add-platform-to-dynamicfeed.js +19 -0
  13. package/migrations/2026-05-17-add-platform-to-facebookretryqueue.js +19 -0
  14. package/migrations/2026-05-17-add-platform-to-pages.js +19 -0
  15. package/migrations/2026-05-17-add-platform-to-presets.js +19 -0
  16. package/migrations/RUN_ME_MANUALLY_2026-05-17.sql +99 -0
  17. package/migrations/add-requested-from-dashboard-to-articles.js +17 -17
  18. package/migrations/change-adset-name-to-text.js +79 -79
  19. package/models/AICampaignQueue.js +143 -136
  20. package/models/AIGenerationLog.js +85 -85
  21. package/models/AIGenerationRequest.js +212 -212
  22. package/models/Ad.js +6 -0
  23. package/models/AdAccount.js +6 -0
  24. package/models/AdAccountValues.js +25 -25
  25. package/models/AdHistory.js +30 -30
  26. package/models/AdPerformance.js +100 -94
  27. package/models/AdSet.js +295 -289
  28. package/models/AdSetHistory.js +30 -30
  29. package/models/AdsetPerformance.js +132 -126
  30. package/models/AiArticleRetryQueue.js +157 -150
  31. package/models/AiCreationRetryQueue.js +127 -127
  32. package/models/ApiAuditLog.js +84 -0
  33. package/models/ApiToken.js +67 -0
  34. package/models/Article.js +212 -206
  35. package/models/AutomationRule.js +173 -173
  36. package/models/BannerTemplate.js +129 -129
  37. package/models/Buyers.js +25 -25
  38. package/models/Campaign.js +163 -157
  39. package/models/CampaignActionHistory.js +86 -86
  40. package/models/CampaignCreationLog.js +309 -309
  41. package/models/CampaignCreationLogV2.js +314 -314
  42. package/models/CampaignHistory.js +33 -33
  43. package/models/Channel.js +55 -55
  44. package/models/CodefuelCampaign.js +6 -0
  45. package/models/Domain.js +39 -39
  46. package/models/DynamicFeed.js +218 -212
  47. package/models/ExplorAdsChannel.js +61 -61
  48. package/models/FacebookRetryQueue.js +163 -156
  49. package/models/Feed.js +33 -33
  50. package/models/FeedArticleConfiguration.js +80 -80
  51. package/models/FrontStoryChannel.js +59 -59
  52. package/models/FrontStoryChannelV2.js +60 -60
  53. package/models/GenericFlowRequest.js +114 -114
  54. package/models/MidoWebChannel.js +47 -47
  55. package/models/MineChannel.js +42 -42
  56. package/models/Pages.js +105 -99
  57. package/models/PipelineExecution.js +59 -59
  58. package/models/PolicyDogsCreativeCache.js +50 -50
  59. package/models/PolicyDogsImageCache.js +30 -30
  60. package/models/Presets.js +40 -34
  61. package/models/RSOCFeedCampaign.js +375 -375
  62. package/models/RsocKeywordPerformance.js +110 -110
  63. package/models/RuleAction.js +90 -90
  64. package/models/RuleCondition.js +137 -137
  65. package/models/RuleExecution.js +107 -107
  66. package/models/RulesValues.js +56 -56
  67. package/models/SupportedLocale.js +23 -23
  68. package/models/SyncHistory.js +249 -249
  69. package/models/TTQChannel.js +42 -42
  70. package/models/TemplateMetadata.js +260 -260
  71. package/models/Tier2_AdAccounts.js +110 -110
  72. package/models/Tier2_Assets.js +70 -70
  73. package/models/Tier2_BusinessManagers.js +105 -105
  74. package/models/Tier2_CreditLines.js +99 -99
  75. package/models/Tier2_Pages.js +91 -91
  76. package/models/Tier2_Pixels.js +82 -82
  77. package/models/Tier2_Tokens.js +64 -64
  78. package/models/Tier2_UserAdAccounts.js +83 -83
  79. package/models/TokenRotationState.js +121 -121
  80. package/models/TonicRSOCKeywordPerformance.js +122 -122
  81. package/models/Users.js +148 -148
  82. package/models/Vertical.js +25 -25
  83. package/models/newFiles.js +137 -137
  84. package/package.json +19 -21
  85. package/run.sh +214 -214
  86. 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
+ };