@happyvertical/smrt-ads 0.37.2 → 0.37.4
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/dist/index.js +1066 -1104
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/smrt-knowledge.json +7 -7
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,1137 +1,1099 @@
|
|
|
1
|
-
import { ObjectRegistry,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
2
|
+
import { TenantScoped, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
3
|
+
//#region src/__smrt-register__.ts
|
|
4
|
+
ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/types/index.ts
|
|
6
7
|
var AdFormatType = /* @__PURE__ */ ((AdFormatType2) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
AdFormatType2["BANNER"] = "banner";
|
|
9
|
+
AdFormatType2["NATIVE"] = "native";
|
|
10
|
+
AdFormatType2["VIDEO"] = "video";
|
|
11
|
+
return AdFormatType2;
|
|
11
12
|
})(AdFormatType || {});
|
|
12
13
|
var PricingModel = /* @__PURE__ */ ((PricingModel2) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
PricingModel2["FIXED"] = "fixed";
|
|
15
|
+
PricingModel2["CPM"] = "cpm";
|
|
16
|
+
PricingModel2["CPC"] = "cpc";
|
|
17
|
+
PricingModel2["CPA"] = "cpa";
|
|
18
|
+
return PricingModel2;
|
|
18
19
|
})(PricingModel || {});
|
|
19
20
|
var AdGroupStatus = /* @__PURE__ */ ((AdGroupStatus2) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
AdGroupStatus2["DRAFT"] = "draft";
|
|
22
|
+
AdGroupStatus2["ACTIVE"] = "active";
|
|
23
|
+
AdGroupStatus2["PAUSED"] = "paused";
|
|
24
|
+
AdGroupStatus2["COMPLETED"] = "completed";
|
|
25
|
+
return AdGroupStatus2;
|
|
25
26
|
})(AdGroupStatus || {});
|
|
26
27
|
var AdVariationStatus = /* @__PURE__ */ ((AdVariationStatus2) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
AdVariationStatus2["DRAFT"] = "draft";
|
|
29
|
+
AdVariationStatus2["ACTIVE"] = "active";
|
|
30
|
+
AdVariationStatus2["PAUSED"] = "paused";
|
|
31
|
+
return AdVariationStatus2;
|
|
31
32
|
})(AdVariationStatus || {});
|
|
32
33
|
var AdEventType = /* @__PURE__ */ ((AdEventType2) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
AdEventType2["IMPRESSION"] = "impression";
|
|
35
|
+
AdEventType2["CLICK"] = "click";
|
|
36
|
+
AdEventType2["CONVERSION"] = "conversion";
|
|
37
|
+
return AdEventType2;
|
|
37
38
|
})(AdEventType || {});
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/models/AdDeliveryTier.ts
|
|
41
|
+
var __defProp$4 = Object.defineProperty;
|
|
38
42
|
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
39
43
|
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return result;
|
|
44
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
45
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
46
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
47
|
+
return result;
|
|
45
48
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return this.pricingModel === PricingModel.CPC || this.pricingModel === PricingModel.CPA;
|
|
89
|
-
}
|
|
49
|
+
var AdDeliveryTier = class extends SmrtObject {
|
|
50
|
+
/**
|
|
51
|
+
* Display name (e.g., "Sponsorship", "Standard", "House")
|
|
52
|
+
*/
|
|
53
|
+
name = "";
|
|
54
|
+
/**
|
|
55
|
+
* Priority level (lower = higher priority: 1, 2, 3...)
|
|
56
|
+
*/
|
|
57
|
+
priority = 0;
|
|
58
|
+
/**
|
|
59
|
+
* Pricing model for this tier
|
|
60
|
+
*/
|
|
61
|
+
pricingModel = PricingModel.CPM;
|
|
62
|
+
/**
|
|
63
|
+
* Optional description
|
|
64
|
+
*/
|
|
65
|
+
description = "";
|
|
66
|
+
constructor(options = {}) {
|
|
67
|
+
super(options);
|
|
68
|
+
if (options.name !== void 0) this.name = options.name;
|
|
69
|
+
if (options.priority !== void 0) this.priority = options.priority;
|
|
70
|
+
if (options.pricingModel !== void 0) this.pricingModel = options.pricingModel;
|
|
71
|
+
if (options.description !== void 0) this.description = options.description;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if this tier is higher priority than another
|
|
75
|
+
*/
|
|
76
|
+
isHigherPriorityThan(other) {
|
|
77
|
+
return this.priority < other.priority;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if this is a fixed pricing tier
|
|
81
|
+
*/
|
|
82
|
+
isFixedPricing() {
|
|
83
|
+
return this.pricingModel === PricingModel.FIXED;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if this is a performance-based tier (CPC, CPA)
|
|
87
|
+
*/
|
|
88
|
+
isPerformanceBased() {
|
|
89
|
+
return this.pricingModel === PricingModel.CPC || this.pricingModel === PricingModel.CPA;
|
|
90
|
+
}
|
|
90
91
|
};
|
|
91
|
-
AdDeliveryTier = __decorateClass$4([
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
],
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
92
|
+
AdDeliveryTier = __decorateClass$4([smrt({
|
|
93
|
+
api: { include: [
|
|
94
|
+
"list",
|
|
95
|
+
"get",
|
|
96
|
+
"create",
|
|
97
|
+
"update"
|
|
98
|
+
] },
|
|
99
|
+
mcp: { include: ["list", "get"] },
|
|
100
|
+
cli: true
|
|
101
|
+
})], AdDeliveryTier);
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/collections/AdDeliveryTierCollection.ts
|
|
104
|
+
var AdDeliveryTierCollection = class extends SmrtCollection {
|
|
105
|
+
static _itemClass = AdDeliveryTier;
|
|
106
|
+
/**
|
|
107
|
+
* Find tiers ordered by priority (ascending, lower = higher priority)
|
|
108
|
+
*
|
|
109
|
+
* @returns Array of tiers ordered by priority
|
|
110
|
+
*/
|
|
111
|
+
async findByPriority() {
|
|
112
|
+
return await this.list({ orderBy: "priority ASC" });
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Find tiers by pricing model
|
|
116
|
+
*
|
|
117
|
+
* @param pricingModel - Pricing model to filter by
|
|
118
|
+
* @returns Array of matching tiers
|
|
119
|
+
*/
|
|
120
|
+
async findByPricingModel(pricingModel) {
|
|
121
|
+
return await this.list({
|
|
122
|
+
where: { pricingModel },
|
|
123
|
+
orderBy: "priority ASC"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the highest priority tier
|
|
128
|
+
*
|
|
129
|
+
* @returns Highest priority tier or null
|
|
130
|
+
*/
|
|
131
|
+
async getHighestPriority() {
|
|
132
|
+
const results = await this.list({
|
|
133
|
+
orderBy: "priority ASC",
|
|
134
|
+
limit: 1
|
|
135
|
+
});
|
|
136
|
+
return results.length > 0 ? results[0] : null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Find fixed pricing tiers
|
|
140
|
+
*/
|
|
141
|
+
async findFixedPricing() {
|
|
142
|
+
return await this.findByPricingModel(PricingModel.FIXED);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Find CPM-based tiers
|
|
146
|
+
*/
|
|
147
|
+
async findCPM() {
|
|
148
|
+
return await this.findByPricingModel(PricingModel.CPM);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Find performance-based tiers (CPC, CPA)
|
|
152
|
+
*/
|
|
153
|
+
async findPerformanceBased() {
|
|
154
|
+
const cpc = await this.findByPricingModel(PricingModel.CPC);
|
|
155
|
+
const cpa = await this.findByPricingModel(PricingModel.CPA);
|
|
156
|
+
return [...cpc, ...cpa].sort((a, b) => a.priority - b.priority);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/models/AdEvent.ts
|
|
161
|
+
var __defProp$3 = Object.defineProperty;
|
|
156
162
|
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
157
163
|
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
165
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
166
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
167
|
+
return result;
|
|
168
|
+
};
|
|
169
|
+
var AdEvent = class extends SmrtObject {
|
|
170
|
+
tenantId = null;
|
|
171
|
+
variationId = "";
|
|
172
|
+
zoneId = "";
|
|
173
|
+
/**
|
|
174
|
+
* Site ID (denormalized from Zone for query efficiency)
|
|
175
|
+
*/
|
|
176
|
+
siteId = "";
|
|
177
|
+
/**
|
|
178
|
+
* Event type (impression, click, conversion)
|
|
179
|
+
*/
|
|
180
|
+
eventType = AdEventType.IMPRESSION;
|
|
181
|
+
/**
|
|
182
|
+
* Event timestamp
|
|
183
|
+
*/
|
|
184
|
+
timestamp = /* @__PURE__ */ new Date();
|
|
185
|
+
/**
|
|
186
|
+
* Analytics metadata as JSON string
|
|
187
|
+
* (IP, user agent, referrer, etc.)
|
|
188
|
+
*/
|
|
189
|
+
metadata = "";
|
|
190
|
+
constructor(options = {}) {
|
|
191
|
+
super(options);
|
|
192
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
193
|
+
if (options.variationId !== void 0) this.variationId = options.variationId;
|
|
194
|
+
if (options.zoneId !== void 0) this.zoneId = options.zoneId;
|
|
195
|
+
if (options.siteId !== void 0) this.siteId = options.siteId;
|
|
196
|
+
if (options.eventType !== void 0) this.eventType = options.eventType;
|
|
197
|
+
if (options.timestamp !== void 0) this.timestamp = options.timestamp;
|
|
198
|
+
if (options.metadata !== void 0) this.metadata = options.metadata;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get metadata as object
|
|
202
|
+
*/
|
|
203
|
+
getMetadata() {
|
|
204
|
+
if (!this.metadata) return {};
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(this.metadata);
|
|
207
|
+
} catch {
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Set metadata from object
|
|
213
|
+
*/
|
|
214
|
+
setMetadata(data) {
|
|
215
|
+
this.metadata = JSON.stringify(data);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Check if this is an impression event
|
|
219
|
+
*/
|
|
220
|
+
isImpression() {
|
|
221
|
+
return this.eventType === AdEventType.IMPRESSION;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check if this is a click event
|
|
225
|
+
*/
|
|
226
|
+
isClick() {
|
|
227
|
+
return this.eventType === AdEventType.CLICK;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if this is a conversion event
|
|
231
|
+
*/
|
|
232
|
+
isConversion() {
|
|
233
|
+
return this.eventType === AdEventType.CONVERSION;
|
|
234
|
+
}
|
|
164
235
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
236
|
+
__decorateClass$3([tenantId({ nullable: true })], AdEvent.prototype, "tenantId", 2);
|
|
237
|
+
__decorateClass$3([foreignKey("AdVariation")], AdEvent.prototype, "variationId", 2);
|
|
238
|
+
__decorateClass$3([crossPackageRef("@happyvertical/smrt-properties:Zone")], AdEvent.prototype, "zoneId", 2);
|
|
239
|
+
AdEvent = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
|
|
240
|
+
tableStrategy: "sti",
|
|
241
|
+
api: { include: ["create", "list"] },
|
|
242
|
+
mcp: { include: ["create"] },
|
|
243
|
+
cli: false
|
|
244
|
+
})], AdEvent);
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/collections/AdEventCollection.ts
|
|
247
|
+
var AdEventCollection = class extends SmrtCollection {
|
|
248
|
+
static _itemClass = AdEvent;
|
|
249
|
+
/**
|
|
250
|
+
* Find events by variation
|
|
251
|
+
*
|
|
252
|
+
* @param variationId - Variation ID
|
|
253
|
+
* @returns Array of events
|
|
254
|
+
*/
|
|
255
|
+
async findByVariation(variationId) {
|
|
256
|
+
return await this.list({
|
|
257
|
+
where: { variationId },
|
|
258
|
+
orderBy: "timestamp DESC"
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Find events by zone
|
|
263
|
+
*
|
|
264
|
+
* @param zoneId - Zone ID
|
|
265
|
+
* @returns Array of events
|
|
266
|
+
*/
|
|
267
|
+
async findByZone(zoneId) {
|
|
268
|
+
return await this.list({
|
|
269
|
+
where: { zoneId },
|
|
270
|
+
orderBy: "timestamp DESC"
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Find events by site
|
|
275
|
+
*
|
|
276
|
+
* @param siteId - Site ID
|
|
277
|
+
* @returns Array of events
|
|
278
|
+
*/
|
|
279
|
+
async findBySite(siteId) {
|
|
280
|
+
return await this.list({
|
|
281
|
+
where: { siteId },
|
|
282
|
+
orderBy: "timestamp DESC"
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Find events in date range
|
|
287
|
+
*
|
|
288
|
+
* @param start - Start date
|
|
289
|
+
* @param end - End date
|
|
290
|
+
* @returns Array of events
|
|
291
|
+
*/
|
|
292
|
+
async findByDateRange(start, end) {
|
|
293
|
+
return await this.list({
|
|
294
|
+
where: {
|
|
295
|
+
"timestamp >=": start.toISOString(),
|
|
296
|
+
"timestamp <=": end.toISOString()
|
|
297
|
+
},
|
|
298
|
+
orderBy: "timestamp DESC"
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Find events by type
|
|
303
|
+
*
|
|
304
|
+
* @param eventType - Event type
|
|
305
|
+
* @returns Array of events
|
|
306
|
+
*/
|
|
307
|
+
async findByType(eventType) {
|
|
308
|
+
return await this.list({
|
|
309
|
+
where: { eventType },
|
|
310
|
+
orderBy: "timestamp DESC"
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Count events by type for a variation
|
|
315
|
+
*
|
|
316
|
+
* @param variationId - Variation ID
|
|
317
|
+
* @param eventType - Event type
|
|
318
|
+
* @returns Count of events
|
|
319
|
+
*/
|
|
320
|
+
async countByType(variationId, eventType) {
|
|
321
|
+
return (await this.list({ where: {
|
|
322
|
+
variationId,
|
|
323
|
+
eventType
|
|
324
|
+
} })).length;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Count impressions for a variation
|
|
328
|
+
*/
|
|
329
|
+
async countImpressions(variationId) {
|
|
330
|
+
return await this.countByType(variationId, AdEventType.IMPRESSION);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Count clicks for a variation
|
|
334
|
+
*/
|
|
335
|
+
async countClicks(variationId) {
|
|
336
|
+
return await this.countByType(variationId, AdEventType.CLICK);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Count conversions for a variation
|
|
340
|
+
*/
|
|
341
|
+
async countConversions(variationId) {
|
|
342
|
+
return await this.countByType(variationId, AdEventType.CONVERSION);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Find all impressions
|
|
346
|
+
*/
|
|
347
|
+
async findImpressions() {
|
|
348
|
+
return await this.findByType(AdEventType.IMPRESSION);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Find all clicks
|
|
352
|
+
*/
|
|
353
|
+
async findClicks() {
|
|
354
|
+
return await this.findByType(AdEventType.CLICK);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Find all conversions
|
|
358
|
+
*/
|
|
359
|
+
async findConversions() {
|
|
360
|
+
return await this.findByType(AdEventType.CONVERSION);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get aggregate stats for a variation
|
|
364
|
+
*
|
|
365
|
+
* @param variationId - Variation ID
|
|
366
|
+
* @returns Stats object with impressions, clicks, conversions, CTR
|
|
367
|
+
*/
|
|
368
|
+
async getVariationStats(variationId) {
|
|
369
|
+
const impressions = await this.countImpressions(variationId);
|
|
370
|
+
const clicks = await this.countClicks(variationId);
|
|
371
|
+
const conversions = await this.countConversions(variationId);
|
|
372
|
+
return {
|
|
373
|
+
impressions,
|
|
374
|
+
clicks,
|
|
375
|
+
conversions,
|
|
376
|
+
ctr: impressions > 0 ? clicks / impressions : 0,
|
|
377
|
+
conversionRate: clicks > 0 ? conversions / clicks : 0
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Find ad events belonging to a specific tenant
|
|
382
|
+
*
|
|
383
|
+
* @param tenantId - Tenant ID to filter by
|
|
384
|
+
* @returns Array of ad events for the tenant
|
|
385
|
+
*/
|
|
386
|
+
async findByTenant(tenantId) {
|
|
387
|
+
return this.list({ where: { tenantId } });
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Find global ad events (no tenant association).
|
|
391
|
+
*
|
|
392
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
393
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
394
|
+
* flagged as an isolation violation). (#1600)
|
|
395
|
+
*
|
|
396
|
+
* @returns Array of global ad events
|
|
397
|
+
*/
|
|
398
|
+
async findGlobal() {
|
|
399
|
+
return queryGlobal(this);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Find ad events for a tenant including global (shared) events.
|
|
403
|
+
*
|
|
404
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
405
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
406
|
+
*
|
|
407
|
+
* @param tenantId - Tenant ID to include
|
|
408
|
+
* @returns Array of tenant-specific and global ad events
|
|
409
|
+
*/
|
|
410
|
+
async findWithGlobals(tenantId) {
|
|
411
|
+
return queryWithGlobals(this, tenantId, "AdEvent.findWithGlobals");
|
|
412
|
+
}
|
|
232
413
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
__decorateClass$3([
|
|
237
|
-
foreignKey("AdVariation")
|
|
238
|
-
], AdEvent.prototype, "variationId", 2);
|
|
239
|
-
__decorateClass$3([
|
|
240
|
-
crossPackageRef("@happyvertical/smrt-properties:Zone")
|
|
241
|
-
], AdEvent.prototype, "zoneId", 2);
|
|
242
|
-
AdEvent = __decorateClass$3([
|
|
243
|
-
TenantScoped({ mode: "optional" }),
|
|
244
|
-
smrt({
|
|
245
|
-
tableStrategy: "sti",
|
|
246
|
-
api: { include: ["create", "list"] },
|
|
247
|
-
// No update/delete (immutable)
|
|
248
|
-
mcp: { include: ["create"] },
|
|
249
|
-
cli: false
|
|
250
|
-
// High volume, not useful in CLI
|
|
251
|
-
})
|
|
252
|
-
], AdEvent);
|
|
253
|
-
class AdEventCollection extends SmrtCollection {
|
|
254
|
-
static _itemClass = AdEvent;
|
|
255
|
-
/**
|
|
256
|
-
* Find events by variation
|
|
257
|
-
*
|
|
258
|
-
* @param variationId - Variation ID
|
|
259
|
-
* @returns Array of events
|
|
260
|
-
*/
|
|
261
|
-
async findByVariation(variationId) {
|
|
262
|
-
return await this.list({
|
|
263
|
-
where: { variationId },
|
|
264
|
-
orderBy: "timestamp DESC"
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Find events by zone
|
|
269
|
-
*
|
|
270
|
-
* @param zoneId - Zone ID
|
|
271
|
-
* @returns Array of events
|
|
272
|
-
*/
|
|
273
|
-
async findByZone(zoneId) {
|
|
274
|
-
return await this.list({
|
|
275
|
-
where: { zoneId },
|
|
276
|
-
orderBy: "timestamp DESC"
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Find events by site
|
|
281
|
-
*
|
|
282
|
-
* @param siteId - Site ID
|
|
283
|
-
* @returns Array of events
|
|
284
|
-
*/
|
|
285
|
-
async findBySite(siteId) {
|
|
286
|
-
return await this.list({
|
|
287
|
-
where: { siteId },
|
|
288
|
-
orderBy: "timestamp DESC"
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Find events in date range
|
|
293
|
-
*
|
|
294
|
-
* @param start - Start date
|
|
295
|
-
* @param end - End date
|
|
296
|
-
* @returns Array of events
|
|
297
|
-
*/
|
|
298
|
-
async findByDateRange(start, end) {
|
|
299
|
-
return await this.list({
|
|
300
|
-
where: {
|
|
301
|
-
"timestamp >=": start.toISOString(),
|
|
302
|
-
"timestamp <=": end.toISOString()
|
|
303
|
-
},
|
|
304
|
-
orderBy: "timestamp DESC"
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Find events by type
|
|
309
|
-
*
|
|
310
|
-
* @param eventType - Event type
|
|
311
|
-
* @returns Array of events
|
|
312
|
-
*/
|
|
313
|
-
async findByType(eventType) {
|
|
314
|
-
return await this.list({
|
|
315
|
-
where: { eventType },
|
|
316
|
-
orderBy: "timestamp DESC"
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Count events by type for a variation
|
|
321
|
-
*
|
|
322
|
-
* @param variationId - Variation ID
|
|
323
|
-
* @param eventType - Event type
|
|
324
|
-
* @returns Count of events
|
|
325
|
-
*/
|
|
326
|
-
async countByType(variationId, eventType) {
|
|
327
|
-
const events = await this.list({
|
|
328
|
-
where: { variationId, eventType }
|
|
329
|
-
});
|
|
330
|
-
return events.length;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Count impressions for a variation
|
|
334
|
-
*/
|
|
335
|
-
async countImpressions(variationId) {
|
|
336
|
-
return await this.countByType(variationId, AdEventType.IMPRESSION);
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Count clicks for a variation
|
|
340
|
-
*/
|
|
341
|
-
async countClicks(variationId) {
|
|
342
|
-
return await this.countByType(variationId, AdEventType.CLICK);
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Count conversions for a variation
|
|
346
|
-
*/
|
|
347
|
-
async countConversions(variationId) {
|
|
348
|
-
return await this.countByType(variationId, AdEventType.CONVERSION);
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Find all impressions
|
|
352
|
-
*/
|
|
353
|
-
async findImpressions() {
|
|
354
|
-
return await this.findByType(AdEventType.IMPRESSION);
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Find all clicks
|
|
358
|
-
*/
|
|
359
|
-
async findClicks() {
|
|
360
|
-
return await this.findByType(AdEventType.CLICK);
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Find all conversions
|
|
364
|
-
*/
|
|
365
|
-
async findConversions() {
|
|
366
|
-
return await this.findByType(AdEventType.CONVERSION);
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Get aggregate stats for a variation
|
|
370
|
-
*
|
|
371
|
-
* @param variationId - Variation ID
|
|
372
|
-
* @returns Stats object with impressions, clicks, conversions, CTR
|
|
373
|
-
*/
|
|
374
|
-
async getVariationStats(variationId) {
|
|
375
|
-
const impressions = await this.countImpressions(variationId);
|
|
376
|
-
const clicks = await this.countClicks(variationId);
|
|
377
|
-
const conversions = await this.countConversions(variationId);
|
|
378
|
-
return {
|
|
379
|
-
impressions,
|
|
380
|
-
clicks,
|
|
381
|
-
conversions,
|
|
382
|
-
ctr: impressions > 0 ? clicks / impressions : 0,
|
|
383
|
-
conversionRate: clicks > 0 ? conversions / clicks : 0
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
387
|
-
// Tenancy Helper Methods
|
|
388
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
389
|
-
/**
|
|
390
|
-
* Find ad events belonging to a specific tenant
|
|
391
|
-
*
|
|
392
|
-
* @param tenantId - Tenant ID to filter by
|
|
393
|
-
* @returns Array of ad events for the tenant
|
|
394
|
-
*/
|
|
395
|
-
async findByTenant(tenantId2) {
|
|
396
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Find global ad events (no tenant association).
|
|
400
|
-
*
|
|
401
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
402
|
-
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
403
|
-
* flagged as an isolation violation). (#1600)
|
|
404
|
-
*
|
|
405
|
-
* @returns Array of global ad events
|
|
406
|
-
*/
|
|
407
|
-
async findGlobal() {
|
|
408
|
-
return queryGlobal(this);
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Find ad events for a tenant including global (shared) events.
|
|
412
|
-
*
|
|
413
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
414
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
415
|
-
*
|
|
416
|
-
* @param tenantId - Tenant ID to include
|
|
417
|
-
* @returns Array of tenant-specific and global ad events
|
|
418
|
-
*/
|
|
419
|
-
async findWithGlobals(tenantId2) {
|
|
420
|
-
return queryWithGlobals(this, tenantId2, "AdEvent.findWithGlobals");
|
|
421
|
-
}
|
|
422
|
-
}
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/models/AdFormat.ts
|
|
416
|
+
var __defProp$2 = Object.defineProperty;
|
|
423
417
|
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
424
418
|
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
419
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
420
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
421
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
422
|
+
return result;
|
|
423
|
+
};
|
|
424
|
+
var AdFormat = class extends SmrtObject {
|
|
425
|
+
/**
|
|
426
|
+
* Display name (e.g., "Leaderboard", "Medium Rectangle")
|
|
427
|
+
*/
|
|
428
|
+
name = "";
|
|
429
|
+
/**
|
|
430
|
+
* Width in pixels
|
|
431
|
+
*/
|
|
432
|
+
width = 0;
|
|
433
|
+
/**
|
|
434
|
+
* Height in pixels
|
|
435
|
+
*/
|
|
436
|
+
height = 0;
|
|
437
|
+
/**
|
|
438
|
+
* Format type (banner, native, video)
|
|
439
|
+
*/
|
|
440
|
+
formatType = AdFormatType.BANNER;
|
|
441
|
+
/**
|
|
442
|
+
* Optional description
|
|
443
|
+
*/
|
|
444
|
+
description = "";
|
|
445
|
+
constructor(options = {}) {
|
|
446
|
+
super(options);
|
|
447
|
+
if (options.name !== void 0) this.name = options.name;
|
|
448
|
+
if (options.width !== void 0) this.width = options.width;
|
|
449
|
+
if (options.height !== void 0) this.height = options.height;
|
|
450
|
+
if (options.formatType !== void 0) this.formatType = options.formatType;
|
|
451
|
+
if (options.description !== void 0) this.description = options.description;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Get dimensions as string (e.g., "728x90")
|
|
455
|
+
*/
|
|
456
|
+
getDimensions() {
|
|
457
|
+
return `${this.width}x${this.height}`;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Check if format matches specific dimensions
|
|
461
|
+
*/
|
|
462
|
+
matchesDimensions(width, height) {
|
|
463
|
+
return this.width === width && this.height === height;
|
|
464
|
+
}
|
|
430
465
|
};
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
466
|
+
AdFormat = __decorateClass$2([smrt({
|
|
467
|
+
api: { include: [
|
|
468
|
+
"list",
|
|
469
|
+
"get",
|
|
470
|
+
"create",
|
|
471
|
+
"update"
|
|
472
|
+
] },
|
|
473
|
+
mcp: { include: ["list", "get"] },
|
|
474
|
+
cli: true
|
|
475
|
+
})], AdFormat);
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/collections/AdFormatCollection.ts
|
|
478
|
+
var AdFormatCollection = class extends SmrtCollection {
|
|
479
|
+
static _itemClass = AdFormat;
|
|
480
|
+
/**
|
|
481
|
+
* Find format by dimensions
|
|
482
|
+
*
|
|
483
|
+
* @param width - Width in pixels
|
|
484
|
+
* @param height - Height in pixels
|
|
485
|
+
* @returns Matching format or null
|
|
486
|
+
*/
|
|
487
|
+
async findByDimensions(width, height) {
|
|
488
|
+
const results = await this.list({
|
|
489
|
+
where: {
|
|
490
|
+
width,
|
|
491
|
+
height
|
|
492
|
+
},
|
|
493
|
+
limit: 1
|
|
494
|
+
});
|
|
495
|
+
return results.length > 0 ? results[0] : null;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Find formats by type
|
|
499
|
+
*
|
|
500
|
+
* @param formatType - Format type (banner, native, video)
|
|
501
|
+
* @returns Array of matching formats
|
|
502
|
+
*/
|
|
503
|
+
async findByType(formatType) {
|
|
504
|
+
return await this.list({
|
|
505
|
+
where: { formatType },
|
|
506
|
+
orderBy: "name ASC"
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Find all banner formats
|
|
511
|
+
*/
|
|
512
|
+
async findBanners() {
|
|
513
|
+
return await this.findByType(AdFormatType.BANNER);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Find all native formats
|
|
517
|
+
*/
|
|
518
|
+
async findNative() {
|
|
519
|
+
return await this.findByType(AdFormatType.NATIVE);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Find all video formats
|
|
523
|
+
*/
|
|
524
|
+
async findVideo() {
|
|
525
|
+
return await this.findByType(AdFormatType.VIDEO);
|
|
526
|
+
}
|
|
473
527
|
};
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
api: { include: ["list", "get", "create", "update"] },
|
|
477
|
-
mcp: { include: ["list", "get"] },
|
|
478
|
-
cli: true
|
|
479
|
-
})
|
|
480
|
-
], AdFormat);
|
|
481
|
-
class AdFormatCollection extends SmrtCollection {
|
|
482
|
-
static _itemClass = AdFormat;
|
|
483
|
-
/**
|
|
484
|
-
* Find format by dimensions
|
|
485
|
-
*
|
|
486
|
-
* @param width - Width in pixels
|
|
487
|
-
* @param height - Height in pixels
|
|
488
|
-
* @returns Matching format or null
|
|
489
|
-
*/
|
|
490
|
-
async findByDimensions(width, height) {
|
|
491
|
-
const results = await this.list({
|
|
492
|
-
where: { width, height },
|
|
493
|
-
limit: 1
|
|
494
|
-
});
|
|
495
|
-
return results.length > 0 ? results[0] : null;
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Find formats by type
|
|
499
|
-
*
|
|
500
|
-
* @param formatType - Format type (banner, native, video)
|
|
501
|
-
* @returns Array of matching formats
|
|
502
|
-
*/
|
|
503
|
-
async findByType(formatType) {
|
|
504
|
-
return await this.list({
|
|
505
|
-
where: { formatType },
|
|
506
|
-
orderBy: "name ASC"
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Find all banner formats
|
|
511
|
-
*/
|
|
512
|
-
async findBanners() {
|
|
513
|
-
return await this.findByType(AdFormatType.BANNER);
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Find all native formats
|
|
517
|
-
*/
|
|
518
|
-
async findNative() {
|
|
519
|
-
return await this.findByType(AdFormatType.NATIVE);
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Find all video formats
|
|
523
|
-
*/
|
|
524
|
-
async findVideo() {
|
|
525
|
-
return await this.findByType(AdFormatType.VIDEO);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/models/AdGroup.ts
|
|
528
530
|
var __defProp$1 = Object.defineProperty;
|
|
529
531
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
530
532
|
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
533
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
534
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
535
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
536
|
+
return result;
|
|
537
|
+
};
|
|
538
|
+
var AdGroup = class extends SmrtObject {
|
|
539
|
+
tenantId = null;
|
|
540
|
+
contractId = "";
|
|
541
|
+
tierId = "";
|
|
542
|
+
/**
|
|
543
|
+
* Display name (e.g., "Summer Sale - Desktop")
|
|
544
|
+
*/
|
|
545
|
+
name = "";
|
|
546
|
+
/**
|
|
547
|
+
* Vertical/category tag slug from smrt-tags (context="advertising")
|
|
548
|
+
*/
|
|
549
|
+
verticalSlug = "";
|
|
550
|
+
/**
|
|
551
|
+
* Targeting rules as JSON string
|
|
552
|
+
*/
|
|
553
|
+
targeting = "";
|
|
554
|
+
/**
|
|
555
|
+
* Allowed Zone IDs as JSON array string (FK to smrt-properties Zone)
|
|
556
|
+
*/
|
|
557
|
+
zoneIds = "";
|
|
558
|
+
/**
|
|
559
|
+
* Campaign start date
|
|
560
|
+
*/
|
|
561
|
+
startDate = null;
|
|
562
|
+
/**
|
|
563
|
+
* Campaign end date
|
|
564
|
+
*/
|
|
565
|
+
endDate = null;
|
|
566
|
+
/**
|
|
567
|
+
* Daily budget limit
|
|
568
|
+
*/
|
|
569
|
+
dailyBudget = 0;
|
|
570
|
+
/**
|
|
571
|
+
* Total campaign budget
|
|
572
|
+
*/
|
|
573
|
+
totalBudget = 0;
|
|
574
|
+
/**
|
|
575
|
+
* Current status
|
|
576
|
+
*/
|
|
577
|
+
status = AdGroupStatus.DRAFT;
|
|
578
|
+
constructor(options = {}) {
|
|
579
|
+
super(options);
|
|
580
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
581
|
+
if (options.contractId !== void 0) this.contractId = options.contractId;
|
|
582
|
+
if (options.tierId !== void 0) this.tierId = options.tierId;
|
|
583
|
+
if (options.name !== void 0) this.name = options.name;
|
|
584
|
+
if (options.verticalSlug !== void 0) this.verticalSlug = options.verticalSlug;
|
|
585
|
+
if (options.targeting !== void 0) this.targeting = options.targeting;
|
|
586
|
+
if (options.zoneIds !== void 0) this.zoneIds = options.zoneIds;
|
|
587
|
+
if (options.startDate !== void 0) this.startDate = options.startDate;
|
|
588
|
+
if (options.endDate !== void 0) this.endDate = options.endDate;
|
|
589
|
+
if (options.dailyBudget !== void 0) this.dailyBudget = options.dailyBudget;
|
|
590
|
+
if (options.totalBudget !== void 0) this.totalBudget = options.totalBudget;
|
|
591
|
+
if (options.status !== void 0) this.status = options.status;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Get zone IDs as array
|
|
595
|
+
*/
|
|
596
|
+
getZoneIds() {
|
|
597
|
+
if (!this.zoneIds) return [];
|
|
598
|
+
try {
|
|
599
|
+
return JSON.parse(this.zoneIds);
|
|
600
|
+
} catch {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Set zone IDs from array
|
|
606
|
+
*/
|
|
607
|
+
setZoneIds(ids) {
|
|
608
|
+
this.zoneIds = JSON.stringify(ids);
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Add a zone ID
|
|
612
|
+
*/
|
|
613
|
+
addZoneId(zoneId) {
|
|
614
|
+
const ids = this.getZoneIds();
|
|
615
|
+
if (!ids.includes(zoneId)) {
|
|
616
|
+
ids.push(zoneId);
|
|
617
|
+
this.setZoneIds(ids);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Remove a zone ID
|
|
622
|
+
*/
|
|
623
|
+
removeZoneId(zoneId) {
|
|
624
|
+
const ids = this.getZoneIds().filter((id) => id !== zoneId);
|
|
625
|
+
this.setZoneIds(ids);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Check if zone ID is allowed
|
|
629
|
+
*/
|
|
630
|
+
hasZoneId(zoneId) {
|
|
631
|
+
return this.getZoneIds().includes(zoneId);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Get targeting rules as object
|
|
635
|
+
*/
|
|
636
|
+
getTargeting() {
|
|
637
|
+
if (!this.targeting) return {};
|
|
638
|
+
try {
|
|
639
|
+
return JSON.parse(this.targeting);
|
|
640
|
+
} catch {
|
|
641
|
+
return {};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Set targeting rules from object
|
|
646
|
+
*/
|
|
647
|
+
setTargeting(rules) {
|
|
648
|
+
this.targeting = JSON.stringify(rules);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Check if ad group is currently active
|
|
652
|
+
*/
|
|
653
|
+
isActive() {
|
|
654
|
+
if (this.status !== AdGroupStatus.ACTIVE) return false;
|
|
655
|
+
const now = /* @__PURE__ */ new Date();
|
|
656
|
+
if (this.startDate && now < this.startDate) return false;
|
|
657
|
+
if (this.endDate && now > this.endDate) return false;
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Check if ad group is in draft state
|
|
662
|
+
*/
|
|
663
|
+
isDraft() {
|
|
664
|
+
return this.status === AdGroupStatus.DRAFT;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Check if ad group is paused
|
|
668
|
+
*/
|
|
669
|
+
isPaused() {
|
|
670
|
+
return this.status === AdGroupStatus.PAUSED;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Check if ad group is completed
|
|
674
|
+
*/
|
|
675
|
+
isCompleted() {
|
|
676
|
+
return this.status === AdGroupStatus.COMPLETED;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Check if ad group has ended (past end date)
|
|
680
|
+
*/
|
|
681
|
+
hasEnded() {
|
|
682
|
+
if (!this.endDate) return false;
|
|
683
|
+
return /* @__PURE__ */ new Date() > this.endDate;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Check if ad group has started
|
|
687
|
+
*/
|
|
688
|
+
hasStarted() {
|
|
689
|
+
if (!this.startDate) return true;
|
|
690
|
+
return /* @__PURE__ */ new Date() >= this.startDate;
|
|
691
|
+
}
|
|
537
692
|
};
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
*/
|
|
691
|
-
hasStarted() {
|
|
692
|
-
if (!this.startDate) return true;
|
|
693
|
-
return /* @__PURE__ */ new Date() >= this.startDate;
|
|
694
|
-
}
|
|
693
|
+
__decorateClass$1([tenantId({ nullable: true })], AdGroup.prototype, "tenantId", 2);
|
|
694
|
+
__decorateClass$1([crossPackageRef("@happyvertical/smrt-commerce:Contract")], AdGroup.prototype, "contractId", 2);
|
|
695
|
+
__decorateClass$1([foreignKey("AdDeliveryTier")], AdGroup.prototype, "tierId", 2);
|
|
696
|
+
AdGroup = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
697
|
+
tableStrategy: "sti",
|
|
698
|
+
api: { include: [
|
|
699
|
+
"list",
|
|
700
|
+
"get",
|
|
701
|
+
"create",
|
|
702
|
+
"update"
|
|
703
|
+
] },
|
|
704
|
+
mcp: { include: [
|
|
705
|
+
"list",
|
|
706
|
+
"get",
|
|
707
|
+
"create"
|
|
708
|
+
] },
|
|
709
|
+
cli: true
|
|
710
|
+
})], AdGroup);
|
|
711
|
+
//#endregion
|
|
712
|
+
//#region src/collections/AdGroupCollection.ts
|
|
713
|
+
var AdGroupCollection = class extends SmrtCollection {
|
|
714
|
+
static _itemClass = AdGroup;
|
|
715
|
+
/**
|
|
716
|
+
* Find ad groups by contract
|
|
717
|
+
*
|
|
718
|
+
* @param contractId - Contract ID
|
|
719
|
+
* @returns Array of ad groups
|
|
720
|
+
*/
|
|
721
|
+
async findByContract(contractId) {
|
|
722
|
+
return await this.list({
|
|
723
|
+
where: { contractId },
|
|
724
|
+
orderBy: "created_at DESC"
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Find ad groups by tier
|
|
729
|
+
*
|
|
730
|
+
* @param tierId - Delivery tier ID
|
|
731
|
+
* @returns Array of ad groups
|
|
732
|
+
*/
|
|
733
|
+
async findByTier(tierId) {
|
|
734
|
+
return await this.list({
|
|
735
|
+
where: { tierId },
|
|
736
|
+
orderBy: "name ASC"
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Find ad groups by status
|
|
741
|
+
*
|
|
742
|
+
* @param status - Ad group status
|
|
743
|
+
* @returns Array of ad groups
|
|
744
|
+
*/
|
|
745
|
+
async findByStatus(status) {
|
|
746
|
+
return await this.list({
|
|
747
|
+
where: { status },
|
|
748
|
+
orderBy: "created_at DESC"
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Find currently active ad groups
|
|
753
|
+
* (status=active, started, not ended)
|
|
754
|
+
*
|
|
755
|
+
* @returns Array of active ad groups
|
|
756
|
+
*/
|
|
757
|
+
async findActive() {
|
|
758
|
+
return (await this.list({
|
|
759
|
+
where: { status: AdGroupStatus.ACTIVE },
|
|
760
|
+
orderBy: "created_at DESC"
|
|
761
|
+
})).filter((group) => group.isActive());
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Find ad groups by vertical slug
|
|
765
|
+
*
|
|
766
|
+
* @param verticalSlug - Tag slug from smrt-tags
|
|
767
|
+
* @returns Array of ad groups
|
|
768
|
+
*/
|
|
769
|
+
async findByVertical(verticalSlug) {
|
|
770
|
+
return await this.list({
|
|
771
|
+
where: { verticalSlug },
|
|
772
|
+
orderBy: "name ASC"
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Find ad groups that can serve to a specific zone
|
|
777
|
+
*
|
|
778
|
+
* @param zoneId - Zone ID to check
|
|
779
|
+
* @returns Array of ad groups containing zone
|
|
780
|
+
*/
|
|
781
|
+
async findByZone(zoneId) {
|
|
782
|
+
return (await this.list({ where: { status: AdGroupStatus.ACTIVE } })).filter((group) => group.hasZoneId(zoneId));
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Find ad groups eligible to serve for a zone
|
|
786
|
+
* (active, has zone, within date range)
|
|
787
|
+
*
|
|
788
|
+
* @param zoneId - Zone ID to check
|
|
789
|
+
* @returns Array of eligible ad groups
|
|
790
|
+
*/
|
|
791
|
+
async findEligibleForZone(zoneId) {
|
|
792
|
+
return (await this.findByZone(zoneId)).filter((group) => group.isActive());
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Find all draft ad groups
|
|
796
|
+
*/
|
|
797
|
+
async findDrafts() {
|
|
798
|
+
return await this.findByStatus(AdGroupStatus.DRAFT);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Find all paused ad groups
|
|
802
|
+
*/
|
|
803
|
+
async findPaused() {
|
|
804
|
+
return await this.findByStatus(AdGroupStatus.PAUSED);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Find all completed ad groups
|
|
808
|
+
*/
|
|
809
|
+
async findCompleted() {
|
|
810
|
+
return await this.findByStatus(AdGroupStatus.COMPLETED);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Find ad groups belonging to a specific tenant
|
|
814
|
+
*
|
|
815
|
+
* @param tenantId - Tenant ID to filter by
|
|
816
|
+
* @returns Array of ad groups for the tenant
|
|
817
|
+
*/
|
|
818
|
+
async findByTenant(tenantId) {
|
|
819
|
+
return this.list({ where: { tenantId } });
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Find global ad groups (no tenant association).
|
|
823
|
+
*
|
|
824
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
825
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
826
|
+
* flagged as an isolation violation). (#1600)
|
|
827
|
+
*
|
|
828
|
+
* @returns Array of global ad groups
|
|
829
|
+
*/
|
|
830
|
+
async findGlobal() {
|
|
831
|
+
return queryGlobal(this);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Find ad groups for a tenant including global (shared) ad groups.
|
|
835
|
+
*
|
|
836
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
837
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
838
|
+
*
|
|
839
|
+
* @param tenantId - Tenant ID to include
|
|
840
|
+
* @returns Array of tenant-specific and global ad groups
|
|
841
|
+
*/
|
|
842
|
+
async findWithGlobals(tenantId) {
|
|
843
|
+
return queryWithGlobals(this, tenantId, "AdGroup.findWithGlobals");
|
|
844
|
+
}
|
|
695
845
|
};
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
], AdGroup.prototype, "tenantId", 2);
|
|
699
|
-
__decorateClass$1([
|
|
700
|
-
crossPackageRef("@happyvertical/smrt-commerce:Contract")
|
|
701
|
-
], AdGroup.prototype, "contractId", 2);
|
|
702
|
-
__decorateClass$1([
|
|
703
|
-
foreignKey("AdDeliveryTier")
|
|
704
|
-
], AdGroup.prototype, "tierId", 2);
|
|
705
|
-
AdGroup = __decorateClass$1([
|
|
706
|
-
TenantScoped({ mode: "optional" }),
|
|
707
|
-
smrt({
|
|
708
|
-
tableStrategy: "sti",
|
|
709
|
-
api: { include: ["list", "get", "create", "update"] },
|
|
710
|
-
mcp: { include: ["list", "get", "create"] },
|
|
711
|
-
cli: true
|
|
712
|
-
})
|
|
713
|
-
], AdGroup);
|
|
714
|
-
class AdGroupCollection extends SmrtCollection {
|
|
715
|
-
static _itemClass = AdGroup;
|
|
716
|
-
/**
|
|
717
|
-
* Find ad groups by contract
|
|
718
|
-
*
|
|
719
|
-
* @param contractId - Contract ID
|
|
720
|
-
* @returns Array of ad groups
|
|
721
|
-
*/
|
|
722
|
-
async findByContract(contractId) {
|
|
723
|
-
return await this.list({
|
|
724
|
-
where: { contractId },
|
|
725
|
-
orderBy: "created_at DESC"
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Find ad groups by tier
|
|
730
|
-
*
|
|
731
|
-
* @param tierId - Delivery tier ID
|
|
732
|
-
* @returns Array of ad groups
|
|
733
|
-
*/
|
|
734
|
-
async findByTier(tierId) {
|
|
735
|
-
return await this.list({
|
|
736
|
-
where: { tierId },
|
|
737
|
-
orderBy: "name ASC"
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Find ad groups by status
|
|
742
|
-
*
|
|
743
|
-
* @param status - Ad group status
|
|
744
|
-
* @returns Array of ad groups
|
|
745
|
-
*/
|
|
746
|
-
async findByStatus(status) {
|
|
747
|
-
return await this.list({
|
|
748
|
-
where: { status },
|
|
749
|
-
orderBy: "created_at DESC"
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* Find currently active ad groups
|
|
754
|
-
* (status=active, started, not ended)
|
|
755
|
-
*
|
|
756
|
-
* @returns Array of active ad groups
|
|
757
|
-
*/
|
|
758
|
-
async findActive() {
|
|
759
|
-
const results = await this.list({
|
|
760
|
-
where: {
|
|
761
|
-
status: AdGroupStatus.ACTIVE
|
|
762
|
-
},
|
|
763
|
-
orderBy: "created_at DESC"
|
|
764
|
-
});
|
|
765
|
-
return results.filter((group) => group.isActive());
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Find ad groups by vertical slug
|
|
769
|
-
*
|
|
770
|
-
* @param verticalSlug - Tag slug from smrt-tags
|
|
771
|
-
* @returns Array of ad groups
|
|
772
|
-
*/
|
|
773
|
-
async findByVertical(verticalSlug) {
|
|
774
|
-
return await this.list({
|
|
775
|
-
where: { verticalSlug },
|
|
776
|
-
orderBy: "name ASC"
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Find ad groups that can serve to a specific zone
|
|
781
|
-
*
|
|
782
|
-
* @param zoneId - Zone ID to check
|
|
783
|
-
* @returns Array of ad groups containing zone
|
|
784
|
-
*/
|
|
785
|
-
async findByZone(zoneId) {
|
|
786
|
-
const all = await this.list({
|
|
787
|
-
where: { status: AdGroupStatus.ACTIVE }
|
|
788
|
-
});
|
|
789
|
-
return all.filter((group) => group.hasZoneId(zoneId));
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Find ad groups eligible to serve for a zone
|
|
793
|
-
* (active, has zone, within date range)
|
|
794
|
-
*
|
|
795
|
-
* @param zoneId - Zone ID to check
|
|
796
|
-
* @returns Array of eligible ad groups
|
|
797
|
-
*/
|
|
798
|
-
async findEligibleForZone(zoneId) {
|
|
799
|
-
const groups = await this.findByZone(zoneId);
|
|
800
|
-
return groups.filter((group) => group.isActive());
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Find all draft ad groups
|
|
804
|
-
*/
|
|
805
|
-
async findDrafts() {
|
|
806
|
-
return await this.findByStatus(AdGroupStatus.DRAFT);
|
|
807
|
-
}
|
|
808
|
-
/**
|
|
809
|
-
* Find all paused ad groups
|
|
810
|
-
*/
|
|
811
|
-
async findPaused() {
|
|
812
|
-
return await this.findByStatus(AdGroupStatus.PAUSED);
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* Find all completed ad groups
|
|
816
|
-
*/
|
|
817
|
-
async findCompleted() {
|
|
818
|
-
return await this.findByStatus(AdGroupStatus.COMPLETED);
|
|
819
|
-
}
|
|
820
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
821
|
-
// Tenancy Helper Methods
|
|
822
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
823
|
-
/**
|
|
824
|
-
* Find ad groups belonging to a specific tenant
|
|
825
|
-
*
|
|
826
|
-
* @param tenantId - Tenant ID to filter by
|
|
827
|
-
* @returns Array of ad groups for the tenant
|
|
828
|
-
*/
|
|
829
|
-
async findByTenant(tenantId2) {
|
|
830
|
-
return this.list({ where: { tenantId: tenantId2 } });
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Find global ad groups (no tenant association).
|
|
834
|
-
*
|
|
835
|
-
* Routes through the shared tenant-global helper so it does not throw under
|
|
836
|
-
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
837
|
-
* flagged as an isolation violation). (#1600)
|
|
838
|
-
*
|
|
839
|
-
* @returns Array of global ad groups
|
|
840
|
-
*/
|
|
841
|
-
async findGlobal() {
|
|
842
|
-
return queryGlobal(this);
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Find ad groups for a tenant including global (shared) ad groups.
|
|
846
|
-
*
|
|
847
|
-
* Fails closed if an active tenant context requests a different tenant's
|
|
848
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
849
|
-
*
|
|
850
|
-
* @param tenantId - Tenant ID to include
|
|
851
|
-
* @returns Array of tenant-specific and global ad groups
|
|
852
|
-
*/
|
|
853
|
-
async findWithGlobals(tenantId2) {
|
|
854
|
-
return queryWithGlobals(this, tenantId2, "AdGroup.findWithGlobals");
|
|
855
|
-
}
|
|
856
|
-
}
|
|
846
|
+
//#endregion
|
|
847
|
+
//#region src/models/AdVariation.ts
|
|
857
848
|
var __defProp = Object.defineProperty;
|
|
858
849
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
859
850
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (kind && result) __defProp(target, key, result);
|
|
865
|
-
return result;
|
|
851
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
852
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
853
|
+
if (kind && result) __defProp(target, key, result);
|
|
854
|
+
return result;
|
|
866
855
|
};
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
856
|
+
var AdVariation = class extends SmrtObject {
|
|
857
|
+
tenantId = null;
|
|
858
|
+
groupId = "";
|
|
859
|
+
formatId = "";
|
|
860
|
+
assetId = "";
|
|
861
|
+
/**
|
|
862
|
+
* Display name (e.g., "Version A - Blue CTA")
|
|
863
|
+
*/
|
|
864
|
+
name = "";
|
|
865
|
+
/**
|
|
866
|
+
* Click destination URL
|
|
867
|
+
*/
|
|
868
|
+
clickUrl = "";
|
|
869
|
+
/**
|
|
870
|
+
* Accessibility alt text
|
|
871
|
+
*/
|
|
872
|
+
altText = "";
|
|
873
|
+
/**
|
|
874
|
+
* A/B testing weight (higher = more likely to be selected)
|
|
875
|
+
*/
|
|
876
|
+
weight = 1;
|
|
877
|
+
/**
|
|
878
|
+
* Current status
|
|
879
|
+
*/
|
|
880
|
+
status = AdVariationStatus.DRAFT;
|
|
881
|
+
/**
|
|
882
|
+
* Denormalized impression count (updated async)
|
|
883
|
+
*/
|
|
884
|
+
impressions = 0;
|
|
885
|
+
/**
|
|
886
|
+
* Denormalized click count (updated async)
|
|
887
|
+
*/
|
|
888
|
+
clicks = 0;
|
|
889
|
+
constructor(options = {}) {
|
|
890
|
+
super(options);
|
|
891
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
892
|
+
if (options.groupId !== void 0) this.groupId = options.groupId;
|
|
893
|
+
if (options.formatId !== void 0) this.formatId = options.formatId;
|
|
894
|
+
if (options.assetId !== void 0) this.assetId = options.assetId;
|
|
895
|
+
if (options.name !== void 0) this.name = options.name;
|
|
896
|
+
if (options.clickUrl !== void 0) this.clickUrl = options.clickUrl;
|
|
897
|
+
if (options.altText !== void 0) this.altText = options.altText;
|
|
898
|
+
if (options.weight !== void 0) this.weight = options.weight;
|
|
899
|
+
if (options.status !== void 0) this.status = options.status;
|
|
900
|
+
if (options.impressions !== void 0) this.impressions = options.impressions;
|
|
901
|
+
if (options.clicks !== void 0) this.clicks = options.clicks;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Check if variation is active
|
|
905
|
+
*/
|
|
906
|
+
isActive() {
|
|
907
|
+
return this.status === AdVariationStatus.ACTIVE;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Check if variation is in draft state
|
|
911
|
+
*/
|
|
912
|
+
isDraft() {
|
|
913
|
+
return this.status === AdVariationStatus.DRAFT;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Check if variation is paused
|
|
917
|
+
*/
|
|
918
|
+
isPaused() {
|
|
919
|
+
return this.status === AdVariationStatus.PAUSED;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Calculate click-through rate (CTR)
|
|
923
|
+
*/
|
|
924
|
+
getCTR() {
|
|
925
|
+
if (this.impressions === 0) return 0;
|
|
926
|
+
return this.clicks / this.impressions;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Increment impression count
|
|
930
|
+
*/
|
|
931
|
+
recordImpression() {
|
|
932
|
+
this.impressions += 1;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Increment click count
|
|
936
|
+
*/
|
|
937
|
+
recordClick() {
|
|
938
|
+
this.clicks += 1;
|
|
939
|
+
}
|
|
952
940
|
};
|
|
953
|
-
__decorateClass([
|
|
954
|
-
|
|
955
|
-
], AdVariation.prototype, "
|
|
956
|
-
__decorateClass([
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
]
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1108
|
-
*
|
|
1109
|
-
* @param tenantId - Tenant ID to include
|
|
1110
|
-
* @returns Array of tenant-specific and global ad variations
|
|
1111
|
-
*/
|
|
1112
|
-
async findWithGlobals(tenantId2) {
|
|
1113
|
-
return queryWithGlobals(
|
|
1114
|
-
this,
|
|
1115
|
-
tenantId2,
|
|
1116
|
-
"AdVariation.findWithGlobals"
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
export {
|
|
1121
|
-
AdDeliveryTier,
|
|
1122
|
-
AdDeliveryTierCollection,
|
|
1123
|
-
AdEvent,
|
|
1124
|
-
AdEventCollection,
|
|
1125
|
-
AdEventType,
|
|
1126
|
-
AdFormat,
|
|
1127
|
-
AdFormatCollection,
|
|
1128
|
-
AdFormatType,
|
|
1129
|
-
AdGroup,
|
|
1130
|
-
AdGroupCollection,
|
|
1131
|
-
AdGroupStatus,
|
|
1132
|
-
AdVariation,
|
|
1133
|
-
AdVariationCollection,
|
|
1134
|
-
AdVariationStatus,
|
|
1135
|
-
PricingModel
|
|
941
|
+
__decorateClass([tenantId({ nullable: true })], AdVariation.prototype, "tenantId", 2);
|
|
942
|
+
__decorateClass([foreignKey("AdGroup")], AdVariation.prototype, "groupId", 2);
|
|
943
|
+
__decorateClass([foreignKey("AdFormat")], AdVariation.prototype, "formatId", 2);
|
|
944
|
+
__decorateClass([crossPackageRef("@happyvertical/smrt-assets:Asset")], AdVariation.prototype, "assetId", 2);
|
|
945
|
+
AdVariation = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
946
|
+
tableStrategy: "sti",
|
|
947
|
+
api: { include: [
|
|
948
|
+
"list",
|
|
949
|
+
"get",
|
|
950
|
+
"create",
|
|
951
|
+
"update"
|
|
952
|
+
] },
|
|
953
|
+
mcp: { include: [
|
|
954
|
+
"list",
|
|
955
|
+
"get",
|
|
956
|
+
"create"
|
|
957
|
+
] },
|
|
958
|
+
cli: true
|
|
959
|
+
})], AdVariation);
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/collections/AdVariationCollection.ts
|
|
962
|
+
var AdVariationCollection = class extends SmrtCollection {
|
|
963
|
+
static _itemClass = AdVariation;
|
|
964
|
+
/**
|
|
965
|
+
* Find variations by ad group
|
|
966
|
+
*
|
|
967
|
+
* @param groupId - Ad group ID
|
|
968
|
+
* @returns Array of variations
|
|
969
|
+
*/
|
|
970
|
+
async findByGroup(groupId) {
|
|
971
|
+
return await this.list({
|
|
972
|
+
where: { groupId },
|
|
973
|
+
orderBy: "name ASC"
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Find variations by format
|
|
978
|
+
*
|
|
979
|
+
* @param formatId - Ad format ID
|
|
980
|
+
* @returns Array of variations
|
|
981
|
+
*/
|
|
982
|
+
async findByFormat(formatId) {
|
|
983
|
+
return await this.list({
|
|
984
|
+
where: { formatId },
|
|
985
|
+
orderBy: "name ASC"
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Find active variations for a group
|
|
990
|
+
*
|
|
991
|
+
* @param groupId - Ad group ID
|
|
992
|
+
* @returns Array of active variations
|
|
993
|
+
*/
|
|
994
|
+
async findActiveByGroup(groupId) {
|
|
995
|
+
return await this.list({
|
|
996
|
+
where: {
|
|
997
|
+
groupId,
|
|
998
|
+
status: AdVariationStatus.ACTIVE
|
|
999
|
+
},
|
|
1000
|
+
orderBy: "name ASC"
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Select a variation using weighted random selection
|
|
1005
|
+
* Higher weight = more likely to be selected
|
|
1006
|
+
*
|
|
1007
|
+
* @param groupId - Ad group ID
|
|
1008
|
+
* @returns Selected variation or null if none available
|
|
1009
|
+
*/
|
|
1010
|
+
async selectByWeight(groupId) {
|
|
1011
|
+
const active = await this.findActiveByGroup(groupId);
|
|
1012
|
+
if (active.length === 0) return null;
|
|
1013
|
+
if (active.length === 1) return active[0];
|
|
1014
|
+
const totalWeight = active.reduce((sum, v) => sum + v.weight, 0);
|
|
1015
|
+
if (totalWeight <= 0) return active[0];
|
|
1016
|
+
let random = Math.random() * totalWeight;
|
|
1017
|
+
for (const variation of active) {
|
|
1018
|
+
random -= variation.weight;
|
|
1019
|
+
if (random <= 0) return variation;
|
|
1020
|
+
}
|
|
1021
|
+
return active[active.length - 1];
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Find variations by status
|
|
1025
|
+
*
|
|
1026
|
+
* @param status - Variation status
|
|
1027
|
+
* @returns Array of variations
|
|
1028
|
+
*/
|
|
1029
|
+
async findByStatus(status) {
|
|
1030
|
+
return await this.list({
|
|
1031
|
+
where: { status },
|
|
1032
|
+
orderBy: "created_at DESC"
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Find all active variations
|
|
1037
|
+
*/
|
|
1038
|
+
async findActive() {
|
|
1039
|
+
return await this.findByStatus(AdVariationStatus.ACTIVE);
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Find all draft variations
|
|
1043
|
+
*/
|
|
1044
|
+
async findDrafts() {
|
|
1045
|
+
return await this.findByStatus(AdVariationStatus.DRAFT);
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Find all paused variations
|
|
1049
|
+
*/
|
|
1050
|
+
async findPaused() {
|
|
1051
|
+
return await this.findByStatus(AdVariationStatus.PAUSED);
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Find top performing variations by CTR
|
|
1055
|
+
*
|
|
1056
|
+
* @param limit - Maximum number to return
|
|
1057
|
+
* @returns Array of variations sorted by CTR descending
|
|
1058
|
+
*/
|
|
1059
|
+
async findTopPerformers(limit = 10) {
|
|
1060
|
+
return (await this.list({ where: { status: AdVariationStatus.ACTIVE } })).filter((v) => v.impressions > 0).sort((a, b) => b.getCTR() - a.getCTR()).slice(0, limit);
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Find ad variations belonging to a specific tenant
|
|
1064
|
+
*
|
|
1065
|
+
* @param tenantId - Tenant ID to filter by
|
|
1066
|
+
* @returns Array of ad variations for the tenant
|
|
1067
|
+
*/
|
|
1068
|
+
async findByTenant(tenantId) {
|
|
1069
|
+
return this.list({ where: { tenantId } });
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Find global ad variations (no tenant association).
|
|
1073
|
+
*
|
|
1074
|
+
* Routes through the shared tenant-global helper so it does not throw under
|
|
1075
|
+
* an active tenant context (an explicit `tenant_id IS NULL` filter would be
|
|
1076
|
+
* flagged as an isolation violation). (#1600)
|
|
1077
|
+
*
|
|
1078
|
+
* @returns Array of global ad variations
|
|
1079
|
+
*/
|
|
1080
|
+
async findGlobal() {
|
|
1081
|
+
return queryGlobal(this);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Find ad variations for a tenant including global (shared) variations.
|
|
1085
|
+
*
|
|
1086
|
+
* Fails closed if an active tenant context requests a different tenant's
|
|
1087
|
+
* rows; the admin/system path keeps the cross-tenant capability. (#1600)
|
|
1088
|
+
*
|
|
1089
|
+
* @param tenantId - Tenant ID to include
|
|
1090
|
+
* @returns Array of tenant-specific and global ad variations
|
|
1091
|
+
*/
|
|
1092
|
+
async findWithGlobals(tenantId) {
|
|
1093
|
+
return queryWithGlobals(this, tenantId, "AdVariation.findWithGlobals");
|
|
1094
|
+
}
|
|
1136
1095
|
};
|
|
1137
|
-
//#
|
|
1096
|
+
//#endregion
|
|
1097
|
+
export { AdDeliveryTier, AdDeliveryTierCollection, AdEvent, AdEventCollection, AdEventType, AdFormat, AdFormatCollection, AdFormatType, AdGroup, AdGroupCollection, AdGroupStatus, AdVariation, AdVariationCollection, AdVariationStatus, PricingModel };
|
|
1098
|
+
|
|
1099
|
+
//# sourceMappingURL=index.js.map
|