agrs-sequelize-sdk 1.2.11 → 1.2.12
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/controllers/campaignCreationLogsController.js +264 -0
- package/models/CampaignCreationLogNew.js +141 -0
- package/package.json +1 -1
- package/routes/api.js +18 -0
- package/utils/loggerUtils.js +497 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
const { CampaignCreationLogNew } = require("../db");
|
|
2
|
+
const {
|
|
3
|
+
getProcessHierarchy,
|
|
4
|
+
getProcessStats,
|
|
5
|
+
} = require("../utils/loggerUtils");
|
|
6
|
+
const { Op } = require("sequelize");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get detailed logs for campaign creation processes
|
|
10
|
+
* @param {Object} req - Express request object
|
|
11
|
+
* @param {Object} res - Express response object
|
|
12
|
+
*/
|
|
13
|
+
async function getCampaignCreationLogs(req, res) {
|
|
14
|
+
try {
|
|
15
|
+
const {
|
|
16
|
+
process_id,
|
|
17
|
+
country,
|
|
18
|
+
status,
|
|
19
|
+
stage,
|
|
20
|
+
entity_name,
|
|
21
|
+
facebook_campaign_id,
|
|
22
|
+
facebook_adset_id,
|
|
23
|
+
facebook_ad_id,
|
|
24
|
+
from_date,
|
|
25
|
+
to_date,
|
|
26
|
+
page = 0,
|
|
27
|
+
limit = 20,
|
|
28
|
+
} = req.query;
|
|
29
|
+
|
|
30
|
+
// Build where clause
|
|
31
|
+
const whereClause = {};
|
|
32
|
+
if (process_id) whereClause.process_id = process_id;
|
|
33
|
+
if (country) whereClause.country = country;
|
|
34
|
+
if (status) whereClause.status = status;
|
|
35
|
+
if (stage) whereClause.stage = stage;
|
|
36
|
+
if (entity_name)
|
|
37
|
+
whereClause.entity_name = { [Op.iLike]: `%${entity_name}%` };
|
|
38
|
+
if (facebook_campaign_id)
|
|
39
|
+
whereClause.facebook_campaign_id = facebook_campaign_id;
|
|
40
|
+
if (facebook_adset_id) whereClause.facebook_adset_id = facebook_adset_id;
|
|
41
|
+
if (facebook_ad_id) whereClause.facebook_ad_id = facebook_ad_id;
|
|
42
|
+
|
|
43
|
+
// Date range filtering
|
|
44
|
+
if (from_date || to_date) {
|
|
45
|
+
whereClause.createdAt = {};
|
|
46
|
+
if (from_date) whereClause.createdAt[Op.gte] = new Date(from_date);
|
|
47
|
+
if (to_date) whereClause.createdAt[Op.lte] = new Date(to_date);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Pagination params
|
|
51
|
+
const pageInt = parseInt(page, 10);
|
|
52
|
+
const limitInt = parseInt(limit, 10);
|
|
53
|
+
|
|
54
|
+
// Execute query
|
|
55
|
+
const { count, rows } = await CampaignCreationLogNew.findAndCountAll({
|
|
56
|
+
where: whereClause,
|
|
57
|
+
limit: limitInt,
|
|
58
|
+
offset: pageInt * limitInt,
|
|
59
|
+
order: [["createdAt", "DESC"]],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return res.json({
|
|
63
|
+
success: true,
|
|
64
|
+
logs: rows,
|
|
65
|
+
pagination: {
|
|
66
|
+
total: count,
|
|
67
|
+
page: pageInt,
|
|
68
|
+
limit: limitInt,
|
|
69
|
+
pages: Math.ceil(count / limitInt),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("Error in getCampaignCreationLogs:", error);
|
|
74
|
+
return res.status(500).json({
|
|
75
|
+
success: false,
|
|
76
|
+
error: error.message,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get all processes with summary information
|
|
83
|
+
* @param {Object} req - Express request object
|
|
84
|
+
* @param {Object} res - Express response object
|
|
85
|
+
*/
|
|
86
|
+
async function getCampaignCreationProcesses(req, res) {
|
|
87
|
+
try {
|
|
88
|
+
const {
|
|
89
|
+
status,
|
|
90
|
+
country,
|
|
91
|
+
from_date,
|
|
92
|
+
to_date,
|
|
93
|
+
page = 0,
|
|
94
|
+
limit = 10,
|
|
95
|
+
} = req.query;
|
|
96
|
+
|
|
97
|
+
// Get distinct process IDs with filter conditions
|
|
98
|
+
const whereClause = {};
|
|
99
|
+
if (status) whereClause.status = status;
|
|
100
|
+
if (country) whereClause.country = country;
|
|
101
|
+
|
|
102
|
+
// Date range filtering
|
|
103
|
+
if (from_date || to_date) {
|
|
104
|
+
whereClause.createdAt = {};
|
|
105
|
+
if (from_date) whereClause.createdAt[Op.gte] = new Date(from_date);
|
|
106
|
+
if (to_date) whereClause.createdAt[Op.lte] = new Date(to_date);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Get distinct process IDs
|
|
110
|
+
const distinctProcessIds = await CampaignCreationLogNew.findAll({
|
|
111
|
+
attributes: [
|
|
112
|
+
"process_id",
|
|
113
|
+
[
|
|
114
|
+
CampaignCreationLogNew.sequelize.fn(
|
|
115
|
+
"MAX",
|
|
116
|
+
CampaignCreationLogNew.sequelize.col("createdAt")
|
|
117
|
+
),
|
|
118
|
+
"latest_timestamp",
|
|
119
|
+
],
|
|
120
|
+
],
|
|
121
|
+
where: whereClause,
|
|
122
|
+
group: ["process_id"],
|
|
123
|
+
order: [
|
|
124
|
+
[CampaignCreationLogNew.sequelize.literal("latest_timestamp"), "DESC"],
|
|
125
|
+
],
|
|
126
|
+
limit: parseInt(limit, 10),
|
|
127
|
+
offset: parseInt(page, 10) * parseInt(limit, 10),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Get process IDs from result
|
|
131
|
+
const processIds = distinctProcessIds.map((item) => item.process_id);
|
|
132
|
+
|
|
133
|
+
// Get summary information for each process
|
|
134
|
+
const processes = [];
|
|
135
|
+
for (const processId of processIds) {
|
|
136
|
+
// Get the initial log for this process
|
|
137
|
+
const initialLog = await CampaignCreationLogNew.findOne({
|
|
138
|
+
where: {
|
|
139
|
+
process_id: processId,
|
|
140
|
+
stage: "PROCESS_STARTED",
|
|
141
|
+
},
|
|
142
|
+
order: [["createdAt", "ASC"]],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (initialLog) {
|
|
146
|
+
// Get stats for this process
|
|
147
|
+
const stats = await getProcessStats(processId);
|
|
148
|
+
|
|
149
|
+
// Get the latest log to determine current status
|
|
150
|
+
const latestLog = await CampaignCreationLogNew.findOne({
|
|
151
|
+
where: { process_id: processId },
|
|
152
|
+
order: [["createdAt", "DESC"]],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
processes.push({
|
|
156
|
+
process_id: processId,
|
|
157
|
+
created_at: initialLog.createdAt,
|
|
158
|
+
updated_at: latestLog.createdAt,
|
|
159
|
+
status: latestLog.status,
|
|
160
|
+
statistics: stats,
|
|
161
|
+
countries: await getCountriesByProcess(processId),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Count total number of distinct processes
|
|
167
|
+
const totalProcessesCount = await CampaignCreationLogNew.count({
|
|
168
|
+
distinct: true,
|
|
169
|
+
col: "process_id",
|
|
170
|
+
where: whereClause,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return res.json({
|
|
174
|
+
success: true,
|
|
175
|
+
processes,
|
|
176
|
+
pagination: {
|
|
177
|
+
total: totalProcessesCount,
|
|
178
|
+
page: parseInt(page, 10),
|
|
179
|
+
limit: parseInt(limit, 10),
|
|
180
|
+
pages: Math.ceil(totalProcessesCount / parseInt(limit, 10)),
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error("Error in getCampaignCreationProcesses:", error);
|
|
185
|
+
return res.status(500).json({
|
|
186
|
+
success: false,
|
|
187
|
+
error: error.message,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get detailed hierarchical view of a specific process
|
|
194
|
+
* @param {Object} req - Express request object
|
|
195
|
+
* @param {Object} res - Express response object
|
|
196
|
+
*/
|
|
197
|
+
async function getCampaignCreationProcessDetails(req, res) {
|
|
198
|
+
try {
|
|
199
|
+
const { process_id } = req.params;
|
|
200
|
+
|
|
201
|
+
if (!process_id) {
|
|
202
|
+
return res.status(400).json({
|
|
203
|
+
success: false,
|
|
204
|
+
error: "Process ID is required",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Get hierarchical view
|
|
209
|
+
const processDetails = await getProcessHierarchy(process_id);
|
|
210
|
+
|
|
211
|
+
if (!processDetails) {
|
|
212
|
+
return res.status(404).json({
|
|
213
|
+
success: false,
|
|
214
|
+
error: "Process not found",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return res.json({
|
|
219
|
+
success: true,
|
|
220
|
+
process: processDetails,
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error("Error in getCampaignCreationProcessDetails:", error);
|
|
224
|
+
return res.status(500).json({
|
|
225
|
+
success: false,
|
|
226
|
+
error: error.message,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Helper function to get countries involved in a process
|
|
233
|
+
* @param {String} processId - Process ID
|
|
234
|
+
* @returns {Promise<Array>} Array of country information
|
|
235
|
+
*/
|
|
236
|
+
async function getCountriesByProcess(processId) {
|
|
237
|
+
const countries = await CampaignCreationLogNew.findAll({
|
|
238
|
+
attributes: [
|
|
239
|
+
"country",
|
|
240
|
+
[
|
|
241
|
+
CampaignCreationLogNew.sequelize.fn(
|
|
242
|
+
"MAX",
|
|
243
|
+
CampaignCreationLogNew.sequelize.col("status")
|
|
244
|
+
),
|
|
245
|
+
"status",
|
|
246
|
+
],
|
|
247
|
+
],
|
|
248
|
+
where: {
|
|
249
|
+
process_id: processId,
|
|
250
|
+
country: { [Op.ne]: null },
|
|
251
|
+
stage: "CAMPAIGN_CREATION",
|
|
252
|
+
},
|
|
253
|
+
group: ["country"],
|
|
254
|
+
raw: true,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return countries;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
getCampaignCreationLogs,
|
|
262
|
+
getCampaignCreationProcesses,
|
|
263
|
+
getCampaignCreationProcessDetails,
|
|
264
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
module.exports = (sequelize, DataTypes) => {
|
|
2
|
+
const CampaignCreationLogNew = sequelize.define(
|
|
3
|
+
"CampaignCreationLogNew",
|
|
4
|
+
{
|
|
5
|
+
id: {
|
|
6
|
+
type: DataTypes.INTEGER,
|
|
7
|
+
primaryKey: true,
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
},
|
|
10
|
+
process_id: {
|
|
11
|
+
type: DataTypes.UUID,
|
|
12
|
+
allowNull: false,
|
|
13
|
+
comment: "Unique ID for the creation batch process",
|
|
14
|
+
},
|
|
15
|
+
stage: {
|
|
16
|
+
type: DataTypes.ENUM(
|
|
17
|
+
"PROCESS_STARTED",
|
|
18
|
+
"CAMPAIGN_CREATION",
|
|
19
|
+
"ADSET_CREATION",
|
|
20
|
+
"AD_CREATION"
|
|
21
|
+
),
|
|
22
|
+
allowNull: false,
|
|
23
|
+
comment: "Current stage of the campaign creation process",
|
|
24
|
+
},
|
|
25
|
+
status: {
|
|
26
|
+
type: DataTypes.ENUM("STARTED", "IN_PROGRESS", "COMPLETED", "FAILED"),
|
|
27
|
+
allowNull: false,
|
|
28
|
+
defaultValue: "STARTED",
|
|
29
|
+
comment: "Status of the current stage",
|
|
30
|
+
},
|
|
31
|
+
country: {
|
|
32
|
+
type: DataTypes.STRING,
|
|
33
|
+
allowNull: true,
|
|
34
|
+
comment: "Country being processed",
|
|
35
|
+
},
|
|
36
|
+
feed_campaign_id: {
|
|
37
|
+
type: DataTypes.STRING,
|
|
38
|
+
allowNull: true,
|
|
39
|
+
comment: "AGRS_CID of the created feed campaign",
|
|
40
|
+
},
|
|
41
|
+
facebook_campaign_id: {
|
|
42
|
+
type: DataTypes.STRING,
|
|
43
|
+
allowNull: true,
|
|
44
|
+
comment: "Facebook campaign ID if created",
|
|
45
|
+
},
|
|
46
|
+
facebook_adset_id: {
|
|
47
|
+
type: DataTypes.STRING,
|
|
48
|
+
allowNull: true,
|
|
49
|
+
comment: "Facebook adset ID if created",
|
|
50
|
+
},
|
|
51
|
+
facebook_ad_id: {
|
|
52
|
+
type: DataTypes.STRING,
|
|
53
|
+
allowNull: true,
|
|
54
|
+
comment: "Facebook ad ID if created",
|
|
55
|
+
},
|
|
56
|
+
parent_id: {
|
|
57
|
+
type: DataTypes.STRING,
|
|
58
|
+
allowNull: true,
|
|
59
|
+
comment: "Parent entity ID (campaign ID for adsets, adset ID for ads)",
|
|
60
|
+
},
|
|
61
|
+
entity_name: {
|
|
62
|
+
type: DataTypes.STRING,
|
|
63
|
+
allowNull: true,
|
|
64
|
+
comment: "Name of the created entity (campaign, adset, or ad name)",
|
|
65
|
+
},
|
|
66
|
+
entity_index: {
|
|
67
|
+
type: DataTypes.INTEGER,
|
|
68
|
+
allowNull: true,
|
|
69
|
+
comment:
|
|
70
|
+
"Index of this entity in a collection (for multiple adsets/ads)",
|
|
71
|
+
},
|
|
72
|
+
details: {
|
|
73
|
+
type: DataTypes.JSONB,
|
|
74
|
+
allowNull: true,
|
|
75
|
+
comment:
|
|
76
|
+
"Structured details about the stage, including original request, campaign, adset, or ad data",
|
|
77
|
+
},
|
|
78
|
+
error_message: {
|
|
79
|
+
type: DataTypes.TEXT,
|
|
80
|
+
allowNull: true,
|
|
81
|
+
comment: "Error message if process failed",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
tableName: "CampaignCreationLogsNew",
|
|
86
|
+
timestamps: true,
|
|
87
|
+
indexes: [
|
|
88
|
+
{
|
|
89
|
+
fields: ["process_id"],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
fields: ["stage"],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
fields: ["status"],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
fields: ["country"],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
fields: ["feed_campaign_id"],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
fields: ["facebook_campaign_id"],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
fields: ["facebook_adset_id"],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
fields: ["facebook_ad_id"],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
fields: ["parent_id"],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
fields: ["entity_name"],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
fields: ["entity_index"],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
fields: ["createdAt"],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CampaignCreationLogNew.associate = (models) => {
|
|
129
|
+
// Add associations if needed
|
|
130
|
+
if (models.CodefuelCampaign) {
|
|
131
|
+
CampaignCreationLogNew.belongsTo(models.CodefuelCampaign, {
|
|
132
|
+
foreignKey: "feed_campaign_id",
|
|
133
|
+
targetKey: "AGRSCID",
|
|
134
|
+
as: "FeedCampaign",
|
|
135
|
+
constraints: false,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return CampaignCreationLogNew;
|
|
141
|
+
};
|
package/package.json
CHANGED
package/routes/api.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Add these new routes to the API router
|
|
2
|
+
|
|
3
|
+
// Import the controller
|
|
4
|
+
const campaignCreationLogsController = require("../controllers/campaignCreationLogsController");
|
|
5
|
+
|
|
6
|
+
// Add routes for campaign creation logs
|
|
7
|
+
router.get(
|
|
8
|
+
"/campaign-creation-logs",
|
|
9
|
+
campaignCreationLogsController.getCampaignCreationLogs
|
|
10
|
+
);
|
|
11
|
+
router.get(
|
|
12
|
+
"/campaign-creation-processes",
|
|
13
|
+
campaignCreationLogsController.getCampaignCreationProcesses
|
|
14
|
+
);
|
|
15
|
+
router.get(
|
|
16
|
+
"/campaign-creation-processes/:process_id",
|
|
17
|
+
campaignCreationLogsController.getCampaignCreationProcessDetails
|
|
18
|
+
);
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create logs in both old and new campaign creation log models
|
|
3
|
+
* @param {Object} oldLogData Data for the original CampaignCreationLog model
|
|
4
|
+
* @param {Object} newLogData Data for the new CampaignCreationLogNew model
|
|
5
|
+
* @returns {Promise<Object>} Object containing both created logs
|
|
6
|
+
*/
|
|
7
|
+
async function createDualLogs(oldLogData, newLogData) {
|
|
8
|
+
const { CampaignCreationLog, CampaignCreationLogNew } = require("../db");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Create log in original model
|
|
12
|
+
const oldLog = await CampaignCreationLog.create(oldLogData);
|
|
13
|
+
|
|
14
|
+
// Create log in new model with extended data
|
|
15
|
+
const newLog = await CampaignCreationLogNew.create({
|
|
16
|
+
...newLogData,
|
|
17
|
+
// Ensure we have the original data as well
|
|
18
|
+
process_id: oldLogData.process_id,
|
|
19
|
+
country: oldLogData.country,
|
|
20
|
+
feed_campaign_id: oldLogData.feed_campaign_id,
|
|
21
|
+
facebook_campaign_id: oldLogData.facebook_campaign_id,
|
|
22
|
+
error_message: oldLogData.error_message,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return { oldLog, newLog };
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("Error creating logs:", error);
|
|
28
|
+
// Always try to create the original log even if the new one fails
|
|
29
|
+
try {
|
|
30
|
+
const oldLog = await CampaignCreationLog.create(oldLogData);
|
|
31
|
+
return { oldLog, newLog: null };
|
|
32
|
+
} catch (oldLogError) {
|
|
33
|
+
console.error("Failed to create old log as well:", oldLogError);
|
|
34
|
+
throw error; // Re-throw original error
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Log campaign creation stage - with support for hierarchy
|
|
41
|
+
* @param {Object} data Log data
|
|
42
|
+
* @returns {Promise<Object>} Created logs
|
|
43
|
+
*/
|
|
44
|
+
async function logCampaignCreationStage(data) {
|
|
45
|
+
const {
|
|
46
|
+
process_id,
|
|
47
|
+
stage,
|
|
48
|
+
status,
|
|
49
|
+
country,
|
|
50
|
+
facebook_campaign_id,
|
|
51
|
+
feed_campaign_id,
|
|
52
|
+
facebook_adset_id,
|
|
53
|
+
facebook_ad_id,
|
|
54
|
+
parent_id,
|
|
55
|
+
entity_name,
|
|
56
|
+
entity_index,
|
|
57
|
+
details,
|
|
58
|
+
error_message,
|
|
59
|
+
} = data;
|
|
60
|
+
|
|
61
|
+
// Format data for old log model
|
|
62
|
+
const oldLogData = {
|
|
63
|
+
process_id,
|
|
64
|
+
status,
|
|
65
|
+
country,
|
|
66
|
+
feed_campaign_id,
|
|
67
|
+
facebook_campaign_id,
|
|
68
|
+
details: {
|
|
69
|
+
step: stage,
|
|
70
|
+
...(facebook_adset_id && { adsetId: facebook_adset_id }),
|
|
71
|
+
...(facebook_ad_id && { adId: facebook_ad_id }),
|
|
72
|
+
...(entity_name && { name: entity_name }),
|
|
73
|
+
...(details && { ...details }),
|
|
74
|
+
},
|
|
75
|
+
error_message,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Format data for new log model
|
|
79
|
+
const newLogData = {
|
|
80
|
+
process_id,
|
|
81
|
+
stage,
|
|
82
|
+
status,
|
|
83
|
+
country,
|
|
84
|
+
feed_campaign_id,
|
|
85
|
+
facebook_campaign_id,
|
|
86
|
+
facebook_adset_id,
|
|
87
|
+
facebook_ad_id,
|
|
88
|
+
parent_id,
|
|
89
|
+
entity_name,
|
|
90
|
+
entity_index,
|
|
91
|
+
details,
|
|
92
|
+
error_message,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return createDualLogs(oldLogData, newLogData);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Log campaign creation with multiple adsets and ads
|
|
100
|
+
* @param {Object} data Campaign data
|
|
101
|
+
* @returns {Promise<Array>} Created logs
|
|
102
|
+
*/
|
|
103
|
+
async function logCampaignWithChildEntities(data) {
|
|
104
|
+
const {
|
|
105
|
+
process_id,
|
|
106
|
+
country,
|
|
107
|
+
campaign_id,
|
|
108
|
+
campaign_name,
|
|
109
|
+
adsets = [],
|
|
110
|
+
campaign_details,
|
|
111
|
+
status = "COMPLETED",
|
|
112
|
+
error_message,
|
|
113
|
+
} = data;
|
|
114
|
+
|
|
115
|
+
const logs = [];
|
|
116
|
+
|
|
117
|
+
// Log the campaign creation
|
|
118
|
+
const campaignLog = await logCampaignCreationStage({
|
|
119
|
+
process_id,
|
|
120
|
+
stage: "CAMPAIGN_CREATION",
|
|
121
|
+
status,
|
|
122
|
+
country,
|
|
123
|
+
facebook_campaign_id: campaign_id,
|
|
124
|
+
entity_name: campaign_name,
|
|
125
|
+
details: {
|
|
126
|
+
campaign_data: campaign_details || { campaign_name },
|
|
127
|
+
planned_adsets: adsets.map((adset) => ({
|
|
128
|
+
adset_name: adset.name,
|
|
129
|
+
targeting: adset.targeting,
|
|
130
|
+
})),
|
|
131
|
+
},
|
|
132
|
+
error_message,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
logs.push(campaignLog);
|
|
136
|
+
|
|
137
|
+
// If campaign was successful and we have adsets, log each adset
|
|
138
|
+
if (status === "COMPLETED" && adsets.length > 0) {
|
|
139
|
+
for (const [index, adset] of adsets.entries()) {
|
|
140
|
+
// Log adset creation
|
|
141
|
+
const adsetLog = await logCampaignCreationStage({
|
|
142
|
+
process_id,
|
|
143
|
+
stage: "ADSET_CREATION",
|
|
144
|
+
status: adset.status || "COMPLETED",
|
|
145
|
+
country,
|
|
146
|
+
facebook_campaign_id: campaign_id,
|
|
147
|
+
facebook_adset_id: adset.id,
|
|
148
|
+
parent_id: campaign_id,
|
|
149
|
+
entity_name: adset.name,
|
|
150
|
+
entity_index: index,
|
|
151
|
+
details: {
|
|
152
|
+
adset_data: {
|
|
153
|
+
adset_name: adset.name,
|
|
154
|
+
targeting: adset.targeting,
|
|
155
|
+
budget: adset.budget,
|
|
156
|
+
},
|
|
157
|
+
planned_ads: adset.ads?.map((ad) => ({
|
|
158
|
+
ad_name: ad.name,
|
|
159
|
+
creative: ad.creative,
|
|
160
|
+
})),
|
|
161
|
+
},
|
|
162
|
+
error_message: adset.error_message,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
logs.push(adsetLog);
|
|
166
|
+
|
|
167
|
+
// If adset was successful and we have ads, log each ad
|
|
168
|
+
if (adset.status !== "FAILED" && adset.ads && adset.ads.length > 0) {
|
|
169
|
+
for (const [adIndex, ad] of adset.ads.entries()) {
|
|
170
|
+
// Log ad creation
|
|
171
|
+
const adLog = await logCampaignCreationStage({
|
|
172
|
+
process_id,
|
|
173
|
+
stage: "AD_CREATION",
|
|
174
|
+
status: ad.status || "COMPLETED",
|
|
175
|
+
country,
|
|
176
|
+
facebook_campaign_id: campaign_id,
|
|
177
|
+
facebook_adset_id: adset.id,
|
|
178
|
+
facebook_ad_id: ad.id,
|
|
179
|
+
parent_id: adset.id,
|
|
180
|
+
entity_name: ad.name,
|
|
181
|
+
entity_index: adIndex,
|
|
182
|
+
details: {
|
|
183
|
+
ad_data: {
|
|
184
|
+
ad_name: ad.name,
|
|
185
|
+
creative: ad.creative,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
error_message: ad.error_message,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
logs.push(adLog);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return logs;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Determine error stage based on error message or context
|
|
202
|
+
* @param {Error} error Error object
|
|
203
|
+
* @returns {String} Stage name where the error occurred
|
|
204
|
+
*/
|
|
205
|
+
function determineErrorStage(error) {
|
|
206
|
+
if (!error) return "PROCESS_STARTED";
|
|
207
|
+
|
|
208
|
+
// Check if stage is explicitly set
|
|
209
|
+
if (error.stage) return error.stage;
|
|
210
|
+
|
|
211
|
+
// Try to infer from error message
|
|
212
|
+
const message = error.message ? error.message.toLowerCase() : "";
|
|
213
|
+
|
|
214
|
+
if (message.includes("campaign")) return "CAMPAIGN_CREATION";
|
|
215
|
+
if (message.includes("adset")) return "ADSET_CREATION";
|
|
216
|
+
if (message.includes("ad ") || message.includes("creative"))
|
|
217
|
+
return "AD_CREATION";
|
|
218
|
+
|
|
219
|
+
return "PROCESS_STARTED"; // Default
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get summary statistics for a campaign creation process
|
|
224
|
+
* @param {String} processId UUID of the process
|
|
225
|
+
* @returns {Promise<Object>} Object with stats about the process
|
|
226
|
+
*/
|
|
227
|
+
async function getProcessStats(processId) {
|
|
228
|
+
const { CampaignCreationLogNew, sequelize } = require("../db");
|
|
229
|
+
const { Op } = require("sequelize");
|
|
230
|
+
|
|
231
|
+
// Count successful countries (completed campaign stage with adsets and ads)
|
|
232
|
+
const successfulCampaigns = await CampaignCreationLogNew.count({
|
|
233
|
+
where: {
|
|
234
|
+
process_id: processId,
|
|
235
|
+
stage: "CAMPAIGN_CREATION",
|
|
236
|
+
status: "COMPLETED",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Count successful adsets
|
|
241
|
+
const successfulAdsets = await CampaignCreationLogNew.count({
|
|
242
|
+
where: {
|
|
243
|
+
process_id: processId,
|
|
244
|
+
stage: "ADSET_CREATION",
|
|
245
|
+
status: "COMPLETED",
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Count successful ads
|
|
250
|
+
const successfulAds = await CampaignCreationLogNew.count({
|
|
251
|
+
where: {
|
|
252
|
+
process_id: processId,
|
|
253
|
+
stage: "AD_CREATION",
|
|
254
|
+
status: "COMPLETED",
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Count failed entities at each level
|
|
259
|
+
const failedCampaigns = await CampaignCreationLogNew.count({
|
|
260
|
+
where: {
|
|
261
|
+
process_id: processId,
|
|
262
|
+
stage: "CAMPAIGN_CREATION",
|
|
263
|
+
status: "FAILED",
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const failedAdsets = await CampaignCreationLogNew.count({
|
|
268
|
+
where: {
|
|
269
|
+
process_id: processId,
|
|
270
|
+
stage: "ADSET_CREATION",
|
|
271
|
+
status: "FAILED",
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const failedAds = await CampaignCreationLogNew.count({
|
|
276
|
+
where: {
|
|
277
|
+
process_id: processId,
|
|
278
|
+
stage: "AD_CREATION",
|
|
279
|
+
status: "FAILED",
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Count countries
|
|
284
|
+
const successfulCountries = await CampaignCreationLogNew.count({
|
|
285
|
+
where: {
|
|
286
|
+
process_id: processId,
|
|
287
|
+
stage: "CAMPAIGN_CREATION",
|
|
288
|
+
status: "COMPLETED",
|
|
289
|
+
},
|
|
290
|
+
distinct: true,
|
|
291
|
+
col: "country",
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const failedCountries = await CampaignCreationLogNew.count({
|
|
295
|
+
where: {
|
|
296
|
+
process_id: processId,
|
|
297
|
+
stage: "CAMPAIGN_CREATION",
|
|
298
|
+
status: "FAILED",
|
|
299
|
+
},
|
|
300
|
+
distinct: true,
|
|
301
|
+
col: "country",
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Get all countries in the process
|
|
305
|
+
const allCountries = await CampaignCreationLogNew.count({
|
|
306
|
+
where: {
|
|
307
|
+
process_id: processId,
|
|
308
|
+
country: { [Op.ne]: null },
|
|
309
|
+
},
|
|
310
|
+
distinct: true,
|
|
311
|
+
col: "country",
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
total_countries: allCountries,
|
|
316
|
+
successful_countries: successfulCountries,
|
|
317
|
+
failed_countries: failedCountries,
|
|
318
|
+
in_progress_countries: allCountries - successfulCountries - failedCountries,
|
|
319
|
+
entity_stats: {
|
|
320
|
+
campaigns: {
|
|
321
|
+
successful: successfulCampaigns,
|
|
322
|
+
failed: failedCampaigns,
|
|
323
|
+
},
|
|
324
|
+
adsets: {
|
|
325
|
+
successful: successfulAdsets,
|
|
326
|
+
failed: failedAdsets,
|
|
327
|
+
},
|
|
328
|
+
ads: {
|
|
329
|
+
successful: successfulAds,
|
|
330
|
+
failed: failedAds,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get hierarchical view of a campaign creation process
|
|
338
|
+
* @param {String} processId Process ID
|
|
339
|
+
* @returns {Promise<Object>} Hierarchical view of the process
|
|
340
|
+
*/
|
|
341
|
+
async function getProcessHierarchy(processId) {
|
|
342
|
+
const { CampaignCreationLogNew } = require("../db");
|
|
343
|
+
|
|
344
|
+
// Get initial process log
|
|
345
|
+
const processLog = await CampaignCreationLogNew.findOne({
|
|
346
|
+
where: {
|
|
347
|
+
process_id: processId,
|
|
348
|
+
stage: "PROCESS_STARTED",
|
|
349
|
+
},
|
|
350
|
+
order: [["createdAt", "ASC"]],
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (!processLog) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Get all campaign logs
|
|
358
|
+
const campaignLogs = await CampaignCreationLogNew.findAll({
|
|
359
|
+
where: {
|
|
360
|
+
process_id: processId,
|
|
361
|
+
stage: "CAMPAIGN_CREATION",
|
|
362
|
+
},
|
|
363
|
+
order: [
|
|
364
|
+
["country", "ASC"],
|
|
365
|
+
["createdAt", "DESC"],
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Group campaigns by country
|
|
370
|
+
const campaignsByCountry = {};
|
|
371
|
+
|
|
372
|
+
for (const log of campaignLogs) {
|
|
373
|
+
const country = log.country || "unknown";
|
|
374
|
+
|
|
375
|
+
if (!campaignsByCountry[country]) {
|
|
376
|
+
campaignsByCountry[country] = {
|
|
377
|
+
country,
|
|
378
|
+
status: log.status,
|
|
379
|
+
campaign_id: log.facebook_campaign_id,
|
|
380
|
+
campaign_name: log.entity_name,
|
|
381
|
+
details: log.details,
|
|
382
|
+
error: log.error_message,
|
|
383
|
+
adsets: [],
|
|
384
|
+
createdAt: log.createdAt,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// For each campaign, get its adsets
|
|
390
|
+
for (const country in campaignsByCountry) {
|
|
391
|
+
const campaign = campaignsByCountry[country];
|
|
392
|
+
|
|
393
|
+
if (campaign.campaign_id) {
|
|
394
|
+
// Get adsets for this campaign
|
|
395
|
+
const adsetLogs = await CampaignCreationLogNew.findAll({
|
|
396
|
+
where: {
|
|
397
|
+
process_id: processId,
|
|
398
|
+
stage: "ADSET_CREATION",
|
|
399
|
+
parent_id: campaign.campaign_id,
|
|
400
|
+
},
|
|
401
|
+
order: [
|
|
402
|
+
["entity_index", "ASC"],
|
|
403
|
+
["createdAt", "DESC"],
|
|
404
|
+
],
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Process adsets
|
|
408
|
+
for (const adsetLog of adsetLogs) {
|
|
409
|
+
const adset = {
|
|
410
|
+
adset_id: adsetLog.facebook_adset_id,
|
|
411
|
+
adset_name: adsetLog.entity_name,
|
|
412
|
+
status: adsetLog.status,
|
|
413
|
+
details: adsetLog.details,
|
|
414
|
+
error: adsetLog.error_message,
|
|
415
|
+
ads: [],
|
|
416
|
+
createdAt: adsetLog.createdAt,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Get ads for this adset
|
|
420
|
+
if (adsetLog.facebook_adset_id) {
|
|
421
|
+
const adLogs = await CampaignCreationLogNew.findAll({
|
|
422
|
+
where: {
|
|
423
|
+
process_id: processId,
|
|
424
|
+
stage: "AD_CREATION",
|
|
425
|
+
parent_id: adsetLog.facebook_adset_id,
|
|
426
|
+
},
|
|
427
|
+
order: [
|
|
428
|
+
["entity_index", "ASC"],
|
|
429
|
+
["createdAt", "DESC"],
|
|
430
|
+
],
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Process ads
|
|
434
|
+
for (const adLog of adLogs) {
|
|
435
|
+
adset.ads.push({
|
|
436
|
+
ad_id: adLog.facebook_ad_id,
|
|
437
|
+
ad_name: adLog.entity_name,
|
|
438
|
+
status: adLog.status,
|
|
439
|
+
details: adLog.details,
|
|
440
|
+
error: adLog.error_message,
|
|
441
|
+
createdAt: adLog.createdAt,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
campaign.adsets.push(adset);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Return hierarchical view
|
|
452
|
+
return {
|
|
453
|
+
process_id: processId,
|
|
454
|
+
status: processLog.status,
|
|
455
|
+
original_request: processLog.details?.original_request,
|
|
456
|
+
created_at: processLog.createdAt,
|
|
457
|
+
campaigns_by_country: campaignsByCountry,
|
|
458
|
+
statistics: await getProcessStats(processId),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Determine the final status of a process based on its logs
|
|
464
|
+
* @param {String} processId UUID of the process
|
|
465
|
+
* @returns {Promise<String>} Final status (COMPLETED, FAILED, etc.)
|
|
466
|
+
*/
|
|
467
|
+
async function determineFinalStatus(processId) {
|
|
468
|
+
const stats = await getProcessStats(processId);
|
|
469
|
+
|
|
470
|
+
// If all countries failed
|
|
471
|
+
if (stats.failed_countries > 0 && stats.successful_countries === 0) {
|
|
472
|
+
return "FAILED";
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// If some countries succeeded and some failed
|
|
476
|
+
if (stats.failed_countries > 0 && stats.successful_countries > 0) {
|
|
477
|
+
return "COMPLETED"; // Could also be "PARTIALLY_COMPLETED" if you want to add this status
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// If all countries succeeded
|
|
481
|
+
if (stats.successful_countries > 0 && stats.failed_countries === 0) {
|
|
482
|
+
return "COMPLETED";
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// If no countries completed or failed yet
|
|
486
|
+
return "IN_PROGRESS";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
module.exports = {
|
|
490
|
+
createDualLogs,
|
|
491
|
+
logCampaignCreationStage,
|
|
492
|
+
logCampaignWithChildEntities,
|
|
493
|
+
determineErrorStage,
|
|
494
|
+
getProcessStats,
|
|
495
|
+
getProcessHierarchy,
|
|
496
|
+
determineFinalStatus,
|
|
497
|
+
};
|