agrs-sequelize-sdk 1.4.59 → 1.4.61

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 (120) hide show
  1. package/migrations/2026-05-17-PIXELS-FOLLOWUP.md +85 -85
  2. package/migrations/2026-05-17-add-platform-to-ad.js +19 -19
  3. package/migrations/2026-05-17-add-platform-to-adaccount.js +19 -19
  4. package/migrations/2026-05-17-add-platform-to-adperformance.js +19 -19
  5. package/migrations/2026-05-17-add-platform-to-adset.js +19 -19
  6. package/migrations/2026-05-17-add-platform-to-adsetperformance.js +19 -19
  7. package/migrations/2026-05-17-add-platform-to-aiarticleretryqueue.js +19 -19
  8. package/migrations/2026-05-17-add-platform-to-aicampaignqueue.js +19 -19
  9. package/migrations/2026-05-17-add-platform-to-article.js +19 -19
  10. package/migrations/2026-05-17-add-platform-to-campaign.js +19 -19
  11. package/migrations/2026-05-17-add-platform-to-codefuelcampaign.js +19 -19
  12. package/migrations/2026-05-17-add-platform-to-dynamicfeed.js +19 -19
  13. package/migrations/2026-05-17-add-platform-to-facebookretryqueue.js +19 -19
  14. package/migrations/2026-05-17-add-platform-to-pages.js +19 -19
  15. package/migrations/2026-05-17-add-platform-to-presets.js +19 -19
  16. package/migrations/2026-05-18-add-group-id-to-ai-campaign-queue.js +18 -18
  17. package/migrations/2026-05-18-add-platform-code-to-creation-logs.js +65 -65
  18. package/migrations/2026-05-18-add-platform-code-to-pixels.js +55 -55
  19. package/migrations/2026-05-18-add-platform-code-to-rsoc-feed-campaigns.js +55 -55
  20. package/migrations/2026-05-18-add-platform-date-index-to-adperformance.js +16 -16
  21. package/migrations/2026-05-18-add-platform-date-index-to-adsetperformance.js +16 -16
  22. package/migrations/2026-05-18-add-review-status-to-ad.js +36 -36
  23. package/migrations/2026-05-18-create-canonical-insights.js +84 -84
  24. package/migrations/2026-05-18-create-snapchat-public-profiles.js +68 -68
  25. package/migrations/2026-05-18-create-tiktok-identities.js +71 -71
  26. package/migrations/2026-05-18-create-tiktok-snapchat-campaigns.js +143 -143
  27. package/migrations/2026-05-18-create-tt-snp-adset-ad-tables.js +309 -309
  28. package/migrations/RUN_ME_MANUALLY_2026-05-17.sql +99 -99
  29. package/migrations/add-requested-from-dashboard-to-articles.js +17 -17
  30. package/migrations/change-adset-name-to-text.js +79 -79
  31. package/models/AICampaignQueue.js +151 -151
  32. package/models/AIGenerationLog.js +85 -85
  33. package/models/AIGenerationRequest.js +212 -212
  34. package/models/AdAccountValues.js +25 -25
  35. package/models/AdHistory.js +30 -30
  36. package/models/AdPerformance.js +104 -104
  37. package/models/AdSet.js +295 -295
  38. package/models/AdSetHistory.js +30 -30
  39. package/models/AdsetPerformance.js +137 -137
  40. package/models/AiArticleRetryQueue.js +157 -157
  41. package/models/AiCreationRetryQueue.js +134 -134
  42. package/models/ApiAuditLog.js +127 -127
  43. package/models/ApiToken.js +67 -67
  44. package/models/Article.js +222 -222
  45. package/models/AutomationRule.js +179 -179
  46. package/models/BannerTemplate.js +129 -129
  47. package/models/Buyers.js +25 -25
  48. package/models/Campaign.js +168 -168
  49. package/models/CampaignActionHistory.js +101 -101
  50. package/models/CampaignCreationLog.js +340 -340
  51. package/models/CampaignCreationLogV2.js +392 -392
  52. package/models/CampaignHistory.js +33 -33
  53. package/models/CampaignTag.js +46 -46
  54. package/models/CampaignTagAssignment.js +48 -48
  55. package/models/CanonicalInsights.js +68 -68
  56. package/models/Channel.js +55 -55
  57. package/models/Domain.js +49 -44
  58. package/models/DynamicFeed.js +218 -218
  59. package/models/ExplorAdsChannel.js +61 -61
  60. package/models/FacebookRetryQueue.js +163 -163
  61. package/models/Feed.js +33 -33
  62. package/models/FeedArticleConfiguration.js +80 -80
  63. package/models/FrontStoryChannel.js +59 -59
  64. package/models/FrontStoryChannelV2.js +60 -60
  65. package/models/GenericFlowRequest.js +114 -114
  66. package/models/MidoWebChannel.js +47 -47
  67. package/models/MineBrandBlocklist.js +56 -56
  68. package/models/MineBrandConfig.js +26 -26
  69. package/models/MineChannel.js +47 -47
  70. package/models/MineRedirectDomain.js +42 -42
  71. package/models/MineWebhookEvent.js +53 -53
  72. package/models/Pages.js +117 -117
  73. package/models/PipelineExecution.js +59 -59
  74. package/models/PolicyDogsCreativeCache.js +50 -50
  75. package/models/PolicyDogsImageCache.js +30 -30
  76. package/models/Presets.js +40 -40
  77. package/models/PublicApiBatch.js +155 -155
  78. package/models/PublicApiBatchItem.js +166 -166
  79. package/models/PublicApiIdempotencyCache.js +135 -135
  80. package/models/RSOCFeedCampaign.js +387 -387
  81. package/models/RSOCFeedConfig.js +46 -46
  82. package/models/RSOCRedirectWsCode.js +22 -22
  83. package/models/RsocKeywordPerformance.js +110 -110
  84. package/models/RuleAction.js +90 -90
  85. package/models/RuleCondition.js +137 -137
  86. package/models/RuleExecution.js +107 -107
  87. package/models/RulesValues.js +56 -56
  88. package/models/SingularChannel.js +31 -31
  89. package/models/SnapchatAd.js +69 -69
  90. package/models/SnapchatAdSquad.js +92 -92
  91. package/models/SnapchatCampaign.js +69 -69
  92. package/models/SnapchatChannel.js +31 -31
  93. package/models/SnapchatPublicProfiles.js +67 -67
  94. package/models/SupportedLocale.js +23 -23
  95. package/models/SyncHistory.js +249 -249
  96. package/models/TTQChannel.js +42 -42
  97. package/models/Team.js +33 -43
  98. package/models/TemplateMetadata.js +260 -260
  99. package/models/Tier2_AdAccounts.js +110 -110
  100. package/models/Tier2_Assets.js +70 -70
  101. package/models/Tier2_BusinessManagers.js +105 -105
  102. package/models/Tier2_CreditLines.js +99 -99
  103. package/models/Tier2_Pages.js +91 -91
  104. package/models/Tier2_Pixels.js +82 -82
  105. package/models/Tier2_Tokens.js +64 -64
  106. package/models/Tier2_UserAdAccounts.js +83 -83
  107. package/models/TikTokAd.js +71 -71
  108. package/models/TikTokAdGroup.js +82 -82
  109. package/models/TikTokCampaign.js +71 -71
  110. package/models/TiktokChannel.js +31 -31
  111. package/models/TiktokIdentities.js +65 -65
  112. package/models/TokenRotationState.js +121 -121
  113. package/models/TonicRSOCKeywordPerformance.js +122 -122
  114. package/models/Users.js +204 -204
  115. package/models/Vertical.js +25 -25
  116. package/models/newFiles.js +137 -137
  117. package/package.json +21 -19
  118. package/run.sh +214 -214
  119. package/services/sequelizeService.js +110 -110
  120. package/tarzo-skalio-integration-docs.md +691 -691
@@ -1,50 +1,50 @@
1
- module.exports = (sequelize, DataTypes) => {
2
- const PolicyDogsCreativeCache = sequelize.define(
3
- "PolicyDogsCreativeCache",
4
- {
5
- id: {
6
- type: DataTypes.INTEGER,
7
- primaryKey: true,
8
- autoIncrement: true,
9
- },
10
- ad_account_id: {
11
- type: DataTypes.STRING,
12
- allowNull: false,
13
- comment: "Facebook ad account ID (e.g., act_123456)",
14
- },
15
- page_id: {
16
- type: DataTypes.STRING,
17
- allowNull: false,
18
- comment: "Facebook page ID used in the creative",
19
- },
20
- dog_url: {
21
- type: DataTypes.TEXT,
22
- allowNull: false,
23
- comment: "The dog article URL used in the creative",
24
- },
25
- creative_id: {
26
- type: DataTypes.STRING,
27
- allowNull: false,
28
- comment: "Facebook creative ID to reuse",
29
- },
30
- created_at: {
31
- type: DataTypes.DATE,
32
- allowNull: false,
33
- defaultValue: DataTypes.NOW,
34
- },
35
- },
36
- {
37
- tableName: "policy_dogs_creative_cache",
38
- timestamps: false,
39
- indexes: [
40
- {
41
- unique: true,
42
- fields: ["ad_account_id", "page_id", "dog_url"],
43
- name: "idx_dogs_creative_cache_unique",
44
- },
45
- ],
46
- }
47
- );
48
-
49
- return PolicyDogsCreativeCache;
50
- };
1
+ module.exports = (sequelize, DataTypes) => {
2
+ const PolicyDogsCreativeCache = sequelize.define(
3
+ "PolicyDogsCreativeCache",
4
+ {
5
+ id: {
6
+ type: DataTypes.INTEGER,
7
+ primaryKey: true,
8
+ autoIncrement: true,
9
+ },
10
+ ad_account_id: {
11
+ type: DataTypes.STRING,
12
+ allowNull: false,
13
+ comment: "Facebook ad account ID (e.g., act_123456)",
14
+ },
15
+ page_id: {
16
+ type: DataTypes.STRING,
17
+ allowNull: false,
18
+ comment: "Facebook page ID used in the creative",
19
+ },
20
+ dog_url: {
21
+ type: DataTypes.TEXT,
22
+ allowNull: false,
23
+ comment: "The dog article URL used in the creative",
24
+ },
25
+ creative_id: {
26
+ type: DataTypes.STRING,
27
+ allowNull: false,
28
+ comment: "Facebook creative ID to reuse",
29
+ },
30
+ created_at: {
31
+ type: DataTypes.DATE,
32
+ allowNull: false,
33
+ defaultValue: DataTypes.NOW,
34
+ },
35
+ },
36
+ {
37
+ tableName: "policy_dogs_creative_cache",
38
+ timestamps: false,
39
+ indexes: [
40
+ {
41
+ unique: true,
42
+ fields: ["ad_account_id", "page_id", "dog_url"],
43
+ name: "idx_dogs_creative_cache_unique",
44
+ },
45
+ ],
46
+ }
47
+ );
48
+
49
+ return PolicyDogsCreativeCache;
50
+ };
@@ -1,30 +1,30 @@
1
- module.exports = (sequelize, DataTypes) => {
2
- const PolicyDogsImageCache = sequelize.define(
3
- "PolicyDogsImageCache",
4
- {
5
- ad_account_id: {
6
- type: DataTypes.STRING,
7
- primaryKey: true,
8
- allowNull: false,
9
- comment: "Facebook ad account ID (e.g., act_123456)",
10
- },
11
- image_hash: {
12
- type: DataTypes.STRING,
13
- allowNull: false,
14
- comment: "Facebook image hash for the dog image uploaded to this account",
15
- },
16
- uploaded_at: {
17
- type: DataTypes.DATE,
18
- allowNull: false,
19
- defaultValue: DataTypes.NOW,
20
- comment: "When the image was uploaded to this ad account",
21
- },
22
- },
23
- {
24
- tableName: "policy_dogs_image_cache",
25
- timestamps: false,
26
- }
27
- );
28
-
29
- return PolicyDogsImageCache;
30
- };
1
+ module.exports = (sequelize, DataTypes) => {
2
+ const PolicyDogsImageCache = sequelize.define(
3
+ "PolicyDogsImageCache",
4
+ {
5
+ ad_account_id: {
6
+ type: DataTypes.STRING,
7
+ primaryKey: true,
8
+ allowNull: false,
9
+ comment: "Facebook ad account ID (e.g., act_123456)",
10
+ },
11
+ image_hash: {
12
+ type: DataTypes.STRING,
13
+ allowNull: false,
14
+ comment: "Facebook image hash for the dog image uploaded to this account",
15
+ },
16
+ uploaded_at: {
17
+ type: DataTypes.DATE,
18
+ allowNull: false,
19
+ defaultValue: DataTypes.NOW,
20
+ comment: "When the image was uploaded to this ad account",
21
+ },
22
+ },
23
+ {
24
+ tableName: "policy_dogs_image_cache",
25
+ timestamps: false,
26
+ }
27
+ );
28
+
29
+ return PolicyDogsImageCache;
30
+ };
package/models/Presets.js CHANGED
@@ -1,40 +1,40 @@
1
- // models/Presets.js
2
- module.exports = (sequelize, DataTypes) => {
3
- const Presets = sequelize.define("Presets", {
4
- id: {
5
- type: DataTypes.UUID,
6
- defaultValue: DataTypes.UUIDV4,
7
- primaryKey: true,
8
- },
9
- name: {
10
- type: DataTypes.STRING,
11
- allowNull: false,
12
- },
13
- feeds: {
14
- type: DataTypes.JSONB,
15
- allowNull: true,
16
- },
17
- userId: {
18
- type: DataTypes.UUID,
19
- allowNull: false,
20
- references: {
21
- model: 'Users',
22
- key: 'id',
23
- },
24
- },
25
- platform: {
26
- type: DataTypes.ENUM("fb", "tt", "snp"),
27
- allowNull: false,
28
- defaultValue: "fb",
29
- comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
30
- },
31
- }, {
32
- tableName: "Presets",
33
- });
34
-
35
- Presets.associate = (models) => {
36
- Presets.belongsTo(models.Users, { foreignKey: 'userId', as: 'user' });
37
- };
38
-
39
- return Presets;
40
- };
1
+ // models/Presets.js
2
+ module.exports = (sequelize, DataTypes) => {
3
+ const Presets = sequelize.define("Presets", {
4
+ id: {
5
+ type: DataTypes.UUID,
6
+ defaultValue: DataTypes.UUIDV4,
7
+ primaryKey: true,
8
+ },
9
+ name: {
10
+ type: DataTypes.STRING,
11
+ allowNull: false,
12
+ },
13
+ feeds: {
14
+ type: DataTypes.JSONB,
15
+ allowNull: true,
16
+ },
17
+ userId: {
18
+ type: DataTypes.UUID,
19
+ allowNull: false,
20
+ references: {
21
+ model: 'Users',
22
+ key: 'id',
23
+ },
24
+ },
25
+ platform: {
26
+ type: DataTypes.ENUM("fb", "tt", "snp"),
27
+ allowNull: false,
28
+ defaultValue: "fb",
29
+ comment: "Ad platform identifier: fb=Facebook, tt=TikTok, snp=Snapchat",
30
+ },
31
+ }, {
32
+ tableName: "Presets",
33
+ });
34
+
35
+ Presets.associate = (models) => {
36
+ Presets.belongsTo(models.Users, { foreignKey: 'userId', as: 'user' });
37
+ };
38
+
39
+ return Presets;
40
+ };
@@ -1,155 +1,155 @@
1
- module.exports = (sequelize, DataTypes) => {
2
- // public_api_batches — the envelope for one POST /public/v1/campaigns/bulk
3
- // request. One batch owns N public_api_batch_items (one per campaign). The
4
- // batch id is the `batchId` returned to the client at 202 and looked up via
5
- // GET /public/v1/processes/{batchId}. Reconciler rolls item terminal states
6
- // up into the *_count columns and flips `status` when pending_count hits 0.
7
- //
8
- // Idempotency note: HTTP-level replay dedup is OWNED by PublicApiIdempotencyCache
9
- // (scoped by (user_id, key), caches the 202 response). This table does NOT
10
- // re-implement that — `idempotency_key` here is descriptive/correlation only,
11
- // deliberately NOT unique (a global-unique key would collide across buyers).
12
- const PublicApiBatch = sequelize.define(
13
- "PublicApiBatch",
14
- {
15
- id: {
16
- // UUID (not autoincrement) so the batchId is unguessable / non-enumerable
17
- // across buyers and stable as an external reference.
18
- type: DataTypes.UUID,
19
- primaryKey: true,
20
- defaultValue: DataTypes.UUIDV4,
21
- },
22
- user_id: {
23
- // Owning buyer (a buyer = a Users row, may hold several ApiTokens).
24
- // GET /processes is scoped by this, matching PublicApiIdempotencyCache's
25
- // (user_id, key) grain. Nullable to leave room for a future internal /
26
- // dashboard-initiated bulk with no buyer user.
27
- type: DataTypes.UUID,
28
- allowNull: true,
29
- field: "user_id",
30
- },
31
- api_token_id: {
32
- // Which ApiToken issued the batch (ApiToken.id is UUID). Debug/support
33
- // only — not part of scoping (a buyer rotating tokens still owns it via
34
- // user_id).
35
- type: DataTypes.UUID,
36
- allowNull: true,
37
- field: "api_token_id",
38
- },
39
- idempotency_key: {
40
- // Descriptive: the Idempotency-Key the buyer sent for this batch, stored
41
- // for correlation/debug. NOT a dedup key here (see header note).
42
- type: DataTypes.STRING(255),
43
- allowNull: true,
44
- field: "idempotency_key",
45
- },
46
- buyer: {
47
- // Buyer marker carried from the request root `buyer` key (mirrors the
48
- // campaign-logs Source classification). Purely descriptive.
49
- type: DataTypes.STRING(255),
50
- allowNull: true,
51
- field: "buyer",
52
- },
53
- source: {
54
- // Origin front-door. 'public_api' today; leaves room for a future
55
- // dashboard-initiated bulk without another table.
56
- type: DataTypes.STRING(50),
57
- allowNull: false,
58
- defaultValue: "public_api",
59
- field: "source",
60
- },
61
- status: {
62
- type: DataTypes.STRING(50),
63
- allowNull: false,
64
- defaultValue: "PENDING",
65
- validate: {
66
- isIn: [
67
- [
68
- "PENDING",
69
- "PROCESSING",
70
- "COMPLETED",
71
- "PARTIALLY_COMPLETED",
72
- "FAILED",
73
- ],
74
- ],
75
- },
76
- },
77
- total_items: {
78
- type: DataTypes.INTEGER,
79
- allowNull: false,
80
- defaultValue: 0,
81
- field: "total_items",
82
- },
83
- pending_count: {
84
- // Reconciler decrements as items reach a terminal state; batch is
85
- // terminal when this hits 0.
86
- type: DataTypes.INTEGER,
87
- allowNull: false,
88
- defaultValue: 0,
89
- field: "pending_count",
90
- },
91
- processing_count: {
92
- type: DataTypes.INTEGER,
93
- allowNull: false,
94
- defaultValue: 0,
95
- field: "processing_count",
96
- },
97
- completed_count: {
98
- type: DataTypes.INTEGER,
99
- allowNull: false,
100
- defaultValue: 0,
101
- field: "completed_count",
102
- },
103
- failed_count: {
104
- type: DataTypes.INTEGER,
105
- allowNull: false,
106
- defaultValue: 0,
107
- field: "failed_count",
108
- },
109
- request_meta: {
110
- // Optional provenance (source IP, token label, client-declared count).
111
- type: DataTypes.JSONB,
112
- allowNull: true,
113
- field: "request_meta",
114
- },
115
- created_at: {
116
- type: DataTypes.DATE,
117
- allowNull: false,
118
- defaultValue: DataTypes.NOW,
119
- field: "created_at",
120
- },
121
- updated_at: {
122
- type: DataTypes.DATE,
123
- allowNull: true,
124
- field: "updated_at",
125
- },
126
- completed_at: {
127
- type: DataTypes.DATE,
128
- allowNull: true,
129
- field: "completed_at",
130
- },
131
- },
132
- {
133
- tableName: "public_api_batches",
134
- timestamps: false, // created_at/updated_at managed manually (matches ai_campaign_queue)
135
- // Indexes are named explicitly: prod sync runs alter:false, so an index
136
- // added later to this (by-then existing) table must be backfilled by a
137
- // migration that targets the same name.
138
- indexes: [
139
- { name: "public_api_batches_user_id_idx", fields: ["user_id"] },
140
- { name: "public_api_batches_status_idx", fields: ["status"] },
141
- { name: "public_api_batches_created_at_idx", fields: ["created_at"] },
142
- ],
143
- }
144
- );
145
-
146
- PublicApiBatch.associate = (models) => {
147
- PublicApiBatch.hasMany(models.PublicApiBatchItem, {
148
- foreignKey: "batch_id",
149
- sourceKey: "id",
150
- as: "items",
151
- });
152
- };
153
-
154
- return PublicApiBatch;
155
- };
1
+ module.exports = (sequelize, DataTypes) => {
2
+ // public_api_batches — the envelope for one POST /public/v1/campaigns/bulk
3
+ // request. One batch owns N public_api_batch_items (one per campaign). The
4
+ // batch id is the `batchId` returned to the client at 202 and looked up via
5
+ // GET /public/v1/processes/{batchId}. Reconciler rolls item terminal states
6
+ // up into the *_count columns and flips `status` when pending_count hits 0.
7
+ //
8
+ // Idempotency note: HTTP-level replay dedup is OWNED by PublicApiIdempotencyCache
9
+ // (scoped by (user_id, key), caches the 202 response). This table does NOT
10
+ // re-implement that — `idempotency_key` here is descriptive/correlation only,
11
+ // deliberately NOT unique (a global-unique key would collide across buyers).
12
+ const PublicApiBatch = sequelize.define(
13
+ "PublicApiBatch",
14
+ {
15
+ id: {
16
+ // UUID (not autoincrement) so the batchId is unguessable / non-enumerable
17
+ // across buyers and stable as an external reference.
18
+ type: DataTypes.UUID,
19
+ primaryKey: true,
20
+ defaultValue: DataTypes.UUIDV4,
21
+ },
22
+ user_id: {
23
+ // Owning buyer (a buyer = a Users row, may hold several ApiTokens).
24
+ // GET /processes is scoped by this, matching PublicApiIdempotencyCache's
25
+ // (user_id, key) grain. Nullable to leave room for a future internal /
26
+ // dashboard-initiated bulk with no buyer user.
27
+ type: DataTypes.UUID,
28
+ allowNull: true,
29
+ field: "user_id",
30
+ },
31
+ api_token_id: {
32
+ // Which ApiToken issued the batch (ApiToken.id is UUID). Debug/support
33
+ // only — not part of scoping (a buyer rotating tokens still owns it via
34
+ // user_id).
35
+ type: DataTypes.UUID,
36
+ allowNull: true,
37
+ field: "api_token_id",
38
+ },
39
+ idempotency_key: {
40
+ // Descriptive: the Idempotency-Key the buyer sent for this batch, stored
41
+ // for correlation/debug. NOT a dedup key here (see header note).
42
+ type: DataTypes.STRING(255),
43
+ allowNull: true,
44
+ field: "idempotency_key",
45
+ },
46
+ buyer: {
47
+ // Buyer marker carried from the request root `buyer` key (mirrors the
48
+ // campaign-logs Source classification). Purely descriptive.
49
+ type: DataTypes.STRING(255),
50
+ allowNull: true,
51
+ field: "buyer",
52
+ },
53
+ source: {
54
+ // Origin front-door. 'public_api' today; leaves room for a future
55
+ // dashboard-initiated bulk without another table.
56
+ type: DataTypes.STRING(50),
57
+ allowNull: false,
58
+ defaultValue: "public_api",
59
+ field: "source",
60
+ },
61
+ status: {
62
+ type: DataTypes.STRING(50),
63
+ allowNull: false,
64
+ defaultValue: "PENDING",
65
+ validate: {
66
+ isIn: [
67
+ [
68
+ "PENDING",
69
+ "PROCESSING",
70
+ "COMPLETED",
71
+ "PARTIALLY_COMPLETED",
72
+ "FAILED",
73
+ ],
74
+ ],
75
+ },
76
+ },
77
+ total_items: {
78
+ type: DataTypes.INTEGER,
79
+ allowNull: false,
80
+ defaultValue: 0,
81
+ field: "total_items",
82
+ },
83
+ pending_count: {
84
+ // Reconciler decrements as items reach a terminal state; batch is
85
+ // terminal when this hits 0.
86
+ type: DataTypes.INTEGER,
87
+ allowNull: false,
88
+ defaultValue: 0,
89
+ field: "pending_count",
90
+ },
91
+ processing_count: {
92
+ type: DataTypes.INTEGER,
93
+ allowNull: false,
94
+ defaultValue: 0,
95
+ field: "processing_count",
96
+ },
97
+ completed_count: {
98
+ type: DataTypes.INTEGER,
99
+ allowNull: false,
100
+ defaultValue: 0,
101
+ field: "completed_count",
102
+ },
103
+ failed_count: {
104
+ type: DataTypes.INTEGER,
105
+ allowNull: false,
106
+ defaultValue: 0,
107
+ field: "failed_count",
108
+ },
109
+ request_meta: {
110
+ // Optional provenance (source IP, token label, client-declared count).
111
+ type: DataTypes.JSONB,
112
+ allowNull: true,
113
+ field: "request_meta",
114
+ },
115
+ created_at: {
116
+ type: DataTypes.DATE,
117
+ allowNull: false,
118
+ defaultValue: DataTypes.NOW,
119
+ field: "created_at",
120
+ },
121
+ updated_at: {
122
+ type: DataTypes.DATE,
123
+ allowNull: true,
124
+ field: "updated_at",
125
+ },
126
+ completed_at: {
127
+ type: DataTypes.DATE,
128
+ allowNull: true,
129
+ field: "completed_at",
130
+ },
131
+ },
132
+ {
133
+ tableName: "public_api_batches",
134
+ timestamps: false, // created_at/updated_at managed manually (matches ai_campaign_queue)
135
+ // Indexes are named explicitly: prod sync runs alter:false, so an index
136
+ // added later to this (by-then existing) table must be backfilled by a
137
+ // migration that targets the same name.
138
+ indexes: [
139
+ { name: "public_api_batches_user_id_idx", fields: ["user_id"] },
140
+ { name: "public_api_batches_status_idx", fields: ["status"] },
141
+ { name: "public_api_batches_created_at_idx", fields: ["created_at"] },
142
+ ],
143
+ }
144
+ );
145
+
146
+ PublicApiBatch.associate = (models) => {
147
+ PublicApiBatch.hasMany(models.PublicApiBatchItem, {
148
+ foreignKey: "batch_id",
149
+ sourceKey: "id",
150
+ as: "items",
151
+ });
152
+ };
153
+
154
+ return PublicApiBatch;
155
+ };