ad2app-lib 1.47.0 → 1.50.0
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/analytics/index.d.ts +76 -0
- package/dist/analytics/index.js +29 -0
- package/dist/types/I_Offer.d.ts +3 -1
- package/dist/types/I_Offer.js +2 -0
- package/dist/types/agency-campaigns.d.ts +427 -0
- package/dist/types/agency-campaigns.js +479 -0
- package/dist/types/agency-pots.d.ts +518 -0
- package/dist/types/agency-pots.js +393 -0
- package/dist/types/agency-threads.d.ts +87 -0
- package/dist/types/agency-threads.js +60 -0
- package/dist/types/agency.d.ts +2 -0
- package/dist/types/agency.js +2 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/package.json +1 -1
- package/src/analytics/index.test.ts +52 -1
- package/src/analytics/index.ts +62 -0
- package/src/types/I_Offer.ts +2 -0
- package/src/types/agency-campaigns.test.ts +85 -0
- package/src/types/agency-campaigns.ts +739 -0
- package/src/types/agency-pots.test.ts +255 -0
- package/src/types/agency-pots.ts +751 -0
- package/src/types/agency-threads.ts +103 -0
- package/src/types/agency.ts +2 -0
- package/src/types/index.ts +3 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.PotFields = exports.PrepareCampaignFileUploadDto = exports.UpdateMaterialCaptionDto = exports.PotRemovalDecisionDto = exports.PotHoldActDto = exports.PotPublishAtDto = exports.MovePotDeadlineDto = exports.RefillPotDto = exports.ApplyToPotDto = exports.CAMPAIGN_FILE_MAX_BYTES = exports.CAMPAIGN_FILE_CONTENT_TYPE = exports.AGENCY_POT_CODES = exports.POT_REMOVAL_DECISIONS = exports.POT_HOLD_ACTIONS = exports.POT_HOLD_REASONS = exports.POT_AVAILABILITY = exports.POT_STEPS = exports.POT_ENTRY_STATES = exports.creatorPotForbiddenKeys = exports.CREATOR_POT_FORBIDDEN_KEY = exports.POT_CURRENCIES = exports.potCostPer1000Minor = exports.potViewsAtMax = exports.potEarnings = exports.potBonusMinor = exports.checkDisclosure = exports.DISCLOSURE_RESULTS = exports.DISCLOSURE_VISIBLE_CHARS = void 0;
|
|
13
|
+
const class_validator_1 = require("class-validator");
|
|
14
|
+
const agency_1 = require("./agency");
|
|
15
|
+
/**
|
|
16
|
+
* Pot campaigns (spec 175 US9): the second way to pay creators on a campaign,
|
|
17
|
+
* beside the negotiated offer of US7.
|
|
18
|
+
*
|
|
19
|
+
* The agency funds ONE pot and sets a maximum per creator; each creator applies
|
|
20
|
+
* with their own guarantee and rate per 1,000 views; the agency accepts or
|
|
21
|
+
* declines, never counters (SC-018). Earnings are the guarantee plus the rate
|
|
22
|
+
* times the counted views, never above the maximum, and accrue only while the
|
|
23
|
+
* pot has unreserved money (FR-030, FR-032).
|
|
24
|
+
*
|
|
25
|
+
* Money is integer minor units plus an ISO-4217 code, everywhere. A creator
|
|
26
|
+
* never receives a pot-level figure: the only pot-level signal they get is the
|
|
27
|
+
* two-valued availability (SC-016), fenced by `creatorPotForbiddenKeys`.
|
|
28
|
+
*
|
|
29
|
+
* See specs/speckit/175-agency-roster/plan.md → US9 addendum.
|
|
30
|
+
*/
|
|
31
|
+
// ── The disclosure check (FR-040) ───────────────────────────────────────────
|
|
32
|
+
/** A caption shows this many characters before "more"; the disclosure must sit inside them. */
|
|
33
|
+
exports.DISCLOSURE_VISIBLE_CHARS = 125;
|
|
34
|
+
/**
|
|
35
|
+
* UOKiK's recommended wordings, with or without "#", with or without Polish
|
|
36
|
+
* diacritics. The list is a counsel decision (spec 175 T030) and lives here
|
|
37
|
+
* only: the upload form, the approval, the publish gate and the report all
|
|
38
|
+
* call `checkDisclosure`.
|
|
39
|
+
*/
|
|
40
|
+
const STRONG_WORDINGS = [
|
|
41
|
+
'reklama',
|
|
42
|
+
'materia[łl]\\s*reklamowy',
|
|
43
|
+
'wsp[óo][łl]praca\\s*reklamowa',
|
|
44
|
+
'p[łl]atna\\s*wsp[óo][łl]praca',
|
|
45
|
+
'post\\s*sponsorowany',
|
|
46
|
+
];
|
|
47
|
+
/** Tags that say something but may not be enough: flagged, never accepted silently. */
|
|
48
|
+
const WEAK_TAGS = ['wsp[óo][łl]praca', 'ad', 'spons', 'promo'];
|
|
49
|
+
/**
|
|
50
|
+
* A wording is a whole word: nothing glued after it, and nothing glued before it
|
|
51
|
+
* unless it opens with `#` (a tag glued to the word before it still starts at its `#`).
|
|
52
|
+
*/
|
|
53
|
+
const word = (body) => new RegExp(`(?:#(?:${body})|(?<![\\p{L}\\p{N}_#])(?:${body}))(?![\\p{L}\\p{N}_])`, 'giu');
|
|
54
|
+
const tag = (body) => new RegExp(`#(?:${body})(?![\\p{L}\\p{N}_])`, 'giu');
|
|
55
|
+
const STRONG = word(STRONG_WORDINGS.join('|'));
|
|
56
|
+
const WEAK = tag(WEAK_TAGS.join('|'));
|
|
57
|
+
exports.DISCLOSURE_RESULTS = ['pass', 'weak', 'missing'];
|
|
58
|
+
const checkDisclosure = (raw) => {
|
|
59
|
+
// Composed form, so a decomposed "ł"/"ó" (pasted from some keyboards) reads as one letter.
|
|
60
|
+
const caption = raw.normalize('NFC');
|
|
61
|
+
const strong = [...caption.matchAll(STRONG)][0];
|
|
62
|
+
if (strong && strong.index !== undefined) {
|
|
63
|
+
return {
|
|
64
|
+
result: strong.index < exports.DISCLOSURE_VISIBLE_CHARS ? 'pass' : 'weak',
|
|
65
|
+
found: strong[0],
|
|
66
|
+
at: strong.index,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const weak = [...caption.matchAll(WEAK)][0];
|
|
70
|
+
if (weak && weak.index !== undefined)
|
|
71
|
+
return { result: 'weak', found: weak[0], at: weak.index };
|
|
72
|
+
return { result: 'missing', found: null, at: null };
|
|
73
|
+
};
|
|
74
|
+
exports.checkDisclosure = checkDisclosure;
|
|
75
|
+
// ── Earnings (FR-030, FR-032, FR-035) ───────────────────────────────────────
|
|
76
|
+
/** The per-view part for `views`: rate × views / 1000, rounded DOWN to the minor unit. Exact for any size. */
|
|
77
|
+
const potBonusMinor = (ratePer1000Minor, views) => Number((BigInt(wholeOrZero(ratePer1000Minor)) * BigInt(wholeOrZero(views))) / 1000n);
|
|
78
|
+
exports.potBonusMinor = potBonusMinor;
|
|
79
|
+
/** A non-negative whole number; anything else (NaN, Infinity, negative) is 0. */
|
|
80
|
+
function wholeOrZero(n) {
|
|
81
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0;
|
|
82
|
+
}
|
|
83
|
+
/** What a creator's terms earn at `views` with an unlimited pot: never above the maximum. */
|
|
84
|
+
const potEarnings = ({ guaranteeMinor, ratePer1000Minor, views, maxMinor }) => Math.min(maxMinor, guaranteeMinor + (0, exports.potBonusMinor)(ratePer1000Minor, views));
|
|
85
|
+
exports.potEarnings = potEarnings;
|
|
86
|
+
/** The exact view count at which the terms reach the maximum (rounded up), or null when they never do. */
|
|
87
|
+
const potViewsAtMax = ({ guaranteeMinor, ratePer1000Minor, maxMinor }) => {
|
|
88
|
+
const left = maxMinor - guaranteeMinor;
|
|
89
|
+
if (left <= 0)
|
|
90
|
+
return 0;
|
|
91
|
+
const rate = BigInt(wholeOrZero(ratePer1000Minor));
|
|
92
|
+
if (rate === 0n)
|
|
93
|
+
return null;
|
|
94
|
+
const numerator = BigInt(Math.trunc(left)) * 1000n;
|
|
95
|
+
return Number((numerator + rate - 1n) / rate);
|
|
96
|
+
};
|
|
97
|
+
exports.potViewsAtMax = potViewsAtMax;
|
|
98
|
+
/** Cost per 1,000 views in minor units, rounded half up; null without views. */
|
|
99
|
+
const potCostPer1000Minor = (costMinor, views) => {
|
|
100
|
+
const v = wholeOrZero(views);
|
|
101
|
+
return v > 0 ? Number((BigInt(wholeOrZero(costMinor)) * 2000n + BigInt(v)) / (2n * BigInt(v))) : null;
|
|
102
|
+
};
|
|
103
|
+
exports.potCostPer1000Minor = potCostPer1000Minor;
|
|
104
|
+
/**
|
|
105
|
+
* The currencies a pot may be kept in: every amount is in minor units of two
|
|
106
|
+
* decimals (grosz, cents), so a zero- or three-decimal currency (JPY, KWD) is
|
|
107
|
+
* refused rather than shown a hundred times off (review 2026-10-06 L14).
|
|
108
|
+
*/
|
|
109
|
+
exports.POT_CURRENCIES = ['PLN', 'EUR', 'USD', 'GBP', 'CHF', 'CZK', 'SEK', 'NOK', 'DKK'];
|
|
110
|
+
// ── The creator's fence (SC-016) ────────────────────────────────────────────
|
|
111
|
+
/** Any key carrying a pot-level figure. A creator-facing response must have none, at any depth. */
|
|
112
|
+
exports.CREATOR_POT_FORBIDDEN_KEY = /budget|remainder|reserved|funded|goal|covered|commitment|acceptedCreators|earned|blended|resultViews|heldViews|countedViews/i;
|
|
113
|
+
/** The paths of every forbidden key in `value`; empty when the response is clean. */
|
|
114
|
+
const creatorPotForbiddenKeys = (value, path = '') => {
|
|
115
|
+
if (value === null || typeof value !== 'object')
|
|
116
|
+
return [];
|
|
117
|
+
return Object.entries(value).flatMap(([key, child]) => {
|
|
118
|
+
const at = path ? `${path}.${key}` : key;
|
|
119
|
+
return [...(exports.CREATOR_POT_FORBIDDEN_KEY.test(key) ? [at] : []), ...(0, exports.creatorPotForbiddenKeys)(child, at)];
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
exports.creatorPotForbiddenKeys = creatorPotForbiddenKeys;
|
|
123
|
+
// ── States ───────────────────────────────────────────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* An entry is one creator's application on one pot.
|
|
126
|
+
* applied → accepted | declined | withdrawn; accepted → released (left or
|
|
127
|
+
* removed before publication).
|
|
128
|
+
*/
|
|
129
|
+
exports.POT_ENTRY_STATES = ['applied', 'accepted', 'declined', 'withdrawn', 'released'];
|
|
130
|
+
/**
|
|
131
|
+
* The pot board's columns, in order (design A2): invitation sent, application,
|
|
132
|
+
* in the campaign, material in, scheduled, bonus counting, bonus final, done
|
|
133
|
+
* (the keep-up period ended with the post still up).
|
|
134
|
+
*/
|
|
135
|
+
exports.POT_STEPS = ['invited', 'applied', 'accepted', 'material_in', 'scheduled', 'counting', 'final', 'done'];
|
|
136
|
+
exports.POT_AVAILABILITY = ['full_max_available', 'filling_up'];
|
|
137
|
+
/** Why a bonus is held (FR-037): the jump against the creator's own history, or paid delivery ad2app did not start. */
|
|
138
|
+
exports.POT_HOLD_REASONS = ['jump', 'paid_delivery'];
|
|
139
|
+
/** The agency's three acts on a held bonus. `message` records the act; the thread opens on the client. */
|
|
140
|
+
exports.POT_HOLD_ACTIONS = ['approve', 'cap', 'message'];
|
|
141
|
+
/**
|
|
142
|
+
* A post taken down after the bonus window but before the keep-up end: the
|
|
143
|
+
* agency is informed and decides whether the guarantee stays earned (FR-032).
|
|
144
|
+
* Before the window ends the guarantee is not earned, with no decision.
|
|
145
|
+
*/
|
|
146
|
+
exports.POT_REMOVAL_DECISIONS = ['keep_base', 'withhold_base'];
|
|
147
|
+
/** Refusal codes the frontend owes its own copy for. */
|
|
148
|
+
exports.AGENCY_POT_CODES = {
|
|
149
|
+
/** Accepting would reserve more than the pot has unreserved. */
|
|
150
|
+
POT_CANNOT_COVER: 'agency-pot-cannot-cover',
|
|
151
|
+
/** The guarantee must be below the maximum per creator. */
|
|
152
|
+
GUARANTEE_TOO_HIGH: 'agency-pot-guarantee-too-high',
|
|
153
|
+
/** Applications closed at the application deadline, or the pot was moved. */
|
|
154
|
+
APPLICATIONS_CLOSED: 'agency-pot-applications-closed',
|
|
155
|
+
/** The kind is fixed once a creator is invited; the terms once an entry is accepted. */
|
|
156
|
+
POT_LOCKED: 'agency-pot-locked',
|
|
157
|
+
/** A refill above the maximum budget. */
|
|
158
|
+
ABOVE_MAX_BUDGET: 'agency-pot-above-max-budget',
|
|
159
|
+
/** The caption fails the disclosure check (FR-040). */
|
|
160
|
+
DISCLOSURE_REQUIRED: 'agency-pot-disclosure-required',
|
|
161
|
+
/** The creator has not connected the platform the post goes to. */
|
|
162
|
+
NO_ACCOUNT: 'agency-pot-no-account',
|
|
163
|
+
/** The confirmed time is not the one on the row now (the agency moved it meanwhile). */
|
|
164
|
+
PUBLISH_TIME_CHANGED: 'agency-pot-publish-time-changed',
|
|
165
|
+
};
|
|
166
|
+
/** PDF, images, video, office documents and archives; no HTML or SVG (opened in the browser). */
|
|
167
|
+
exports.CAMPAIGN_FILE_CONTENT_TYPE = /^(application\/pdf|image\/(png|jpeg|gif|webp|heic|heif|avif)|video\/[A-Za-z0-9.+-]+|application\/(msword|vnd\.openxmlformats-officedocument\.[a-z.]+|vnd\.ms-(excel|powerpoint)|vnd\.oasis\.opendocument\.[a-z.]+|rtf|zip|x-zip-compressed|x-7z-compressed|x-rar-compressed|vnd\.rar|gzip|x-tar)|text\/(plain|csv))$/;
|
|
168
|
+
exports.CAMPAIGN_FILE_MAX_BYTES = 2 * 1024 * 1024 * 1024;
|
|
169
|
+
// ── Bodies ───────────────────────────────────────────────────────────────────
|
|
170
|
+
const MONEY_MAX = 100000000000;
|
|
171
|
+
/**
|
|
172
|
+
* `POST /agency/campaign-invitations/:memberId/application` — the creator's own
|
|
173
|
+
* terms. Applying from an invitation also joins the campaign, answering any
|
|
174
|
+
* access request folded into it with `scopes` (absent = everything asked).
|
|
175
|
+
*/
|
|
176
|
+
class ApplyToPotDto {
|
|
177
|
+
constructor(data) {
|
|
178
|
+
this.guaranteeMinor = data?.guaranteeMinor ?? 0;
|
|
179
|
+
this.ratePer1000Minor = data?.ratePer1000Minor ?? 0;
|
|
180
|
+
this.scopes = data?.scopes;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.ApplyToPotDto = ApplyToPotDto;
|
|
184
|
+
__decorate([
|
|
185
|
+
(0, class_validator_1.IsInt)(),
|
|
186
|
+
(0, class_validator_1.Min)(0),
|
|
187
|
+
(0, class_validator_1.Max)(MONEY_MAX),
|
|
188
|
+
__metadata("design:type", Number)
|
|
189
|
+
], ApplyToPotDto.prototype, "guaranteeMinor", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
(0, class_validator_1.IsInt)(),
|
|
192
|
+
(0, class_validator_1.Min)(0),
|
|
193
|
+
(0, class_validator_1.Max)(MONEY_MAX),
|
|
194
|
+
__metadata("design:type", Number)
|
|
195
|
+
], ApplyToPotDto.prototype, "ratePer1000Minor", void 0);
|
|
196
|
+
__decorate([
|
|
197
|
+
(0, class_validator_1.IsOptional)(),
|
|
198
|
+
(0, class_validator_1.IsArray)(),
|
|
199
|
+
(0, class_validator_1.IsIn)(agency_1.AGENCY_GRANT_SCOPES, { each: true }),
|
|
200
|
+
__metadata("design:type", Array)
|
|
201
|
+
], ApplyToPotDto.prototype, "scopes", void 0);
|
|
202
|
+
/** `POST /agency/campaigns/:id/pot/refill` — up to the maximum budget. */
|
|
203
|
+
class RefillPotDto {
|
|
204
|
+
constructor(data) {
|
|
205
|
+
this.amountMinor = data?.amountMinor ?? 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
exports.RefillPotDto = RefillPotDto;
|
|
209
|
+
__decorate([
|
|
210
|
+
(0, class_validator_1.IsInt)(),
|
|
211
|
+
(0, class_validator_1.Min)(1),
|
|
212
|
+
(0, class_validator_1.Max)(MONEY_MAX),
|
|
213
|
+
__metadata("design:type", Number)
|
|
214
|
+
], RefillPotDto.prototype, "amountMinor", void 0);
|
|
215
|
+
/** `PATCH /agency/campaigns/:id/pot/application-deadline`. */
|
|
216
|
+
class MovePotDeadlineDto {
|
|
217
|
+
constructor(data) {
|
|
218
|
+
this.applicationDeadlineAt = data?.applicationDeadlineAt ?? '';
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
exports.MovePotDeadlineDto = MovePotDeadlineDto;
|
|
222
|
+
__decorate([
|
|
223
|
+
(0, class_validator_1.IsDateString)(),
|
|
224
|
+
__metadata("design:type", String)
|
|
225
|
+
], MovePotDeadlineDto.prototype, "applicationDeadlineAt", void 0);
|
|
226
|
+
/** The agency sets the time agreed with the brand; the creator confirms that same value. */
|
|
227
|
+
class PotPublishAtDto {
|
|
228
|
+
constructor(data) {
|
|
229
|
+
this.publishAt = data?.publishAt ?? '';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
exports.PotPublishAtDto = PotPublishAtDto;
|
|
233
|
+
__decorate([
|
|
234
|
+
(0, class_validator_1.IsDateString)(),
|
|
235
|
+
__metadata("design:type", String)
|
|
236
|
+
], PotPublishAtDto.prototype, "publishAt", void 0);
|
|
237
|
+
class PotHoldActDto {
|
|
238
|
+
constructor(data) {
|
|
239
|
+
this.action = data?.action ?? 'approve';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.PotHoldActDto = PotHoldActDto;
|
|
243
|
+
__decorate([
|
|
244
|
+
(0, class_validator_1.IsIn)(exports.POT_HOLD_ACTIONS),
|
|
245
|
+
__metadata("design:type", String)
|
|
246
|
+
], PotHoldActDto.prototype, "action", void 0);
|
|
247
|
+
/** `POST /agency/campaigns/:id/pot/entries/:entryId/removal` — the agency's decision on a late takedown (FR-032). */
|
|
248
|
+
class PotRemovalDecisionDto {
|
|
249
|
+
constructor(data) {
|
|
250
|
+
this.decision = data?.decision ?? 'keep_base';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
exports.PotRemovalDecisionDto = PotRemovalDecisionDto;
|
|
254
|
+
__decorate([
|
|
255
|
+
(0, class_validator_1.IsIn)(exports.POT_REMOVAL_DECISIONS),
|
|
256
|
+
__metadata("design:type", String)
|
|
257
|
+
], PotRemovalDecisionDto.prototype, "decision", void 0);
|
|
258
|
+
/** The caption is the creator's until publication (FR-032). */
|
|
259
|
+
class UpdateMaterialCaptionDto {
|
|
260
|
+
constructor(data) {
|
|
261
|
+
this.caption = data?.caption ?? '';
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
exports.UpdateMaterialCaptionDto = UpdateMaterialCaptionDto;
|
|
265
|
+
__decorate([
|
|
266
|
+
(0, class_validator_1.IsString)(),
|
|
267
|
+
(0, class_validator_1.MaxLength)(2200),
|
|
268
|
+
__metadata("design:type", String)
|
|
269
|
+
], UpdateMaterialCaptionDto.prototype, "caption", void 0);
|
|
270
|
+
class PrepareCampaignFileUploadDto {
|
|
271
|
+
constructor(data) {
|
|
272
|
+
this.fileName = data?.fileName ?? '';
|
|
273
|
+
this.contentType = data?.contentType ?? '';
|
|
274
|
+
this.sizeBytes = data?.sizeBytes ?? 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
exports.PrepareCampaignFileUploadDto = PrepareCampaignFileUploadDto;
|
|
278
|
+
__decorate([
|
|
279
|
+
(0, class_validator_1.IsString)(),
|
|
280
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
281
|
+
(0, class_validator_1.MaxLength)(255),
|
|
282
|
+
__metadata("design:type", String)
|
|
283
|
+
], PrepareCampaignFileUploadDto.prototype, "fileName", void 0);
|
|
284
|
+
__decorate([
|
|
285
|
+
(0, class_validator_1.IsString)(),
|
|
286
|
+
(0, class_validator_1.Matches)(exports.CAMPAIGN_FILE_CONTENT_TYPE),
|
|
287
|
+
__metadata("design:type", String)
|
|
288
|
+
], PrepareCampaignFileUploadDto.prototype, "contentType", void 0);
|
|
289
|
+
__decorate([
|
|
290
|
+
(0, class_validator_1.IsInt)(),
|
|
291
|
+
(0, class_validator_1.Min)(1),
|
|
292
|
+
(0, class_validator_1.Max)(exports.CAMPAIGN_FILE_MAX_BYTES),
|
|
293
|
+
__metadata("design:type", Number)
|
|
294
|
+
], PrepareCampaignFileUploadDto.prototype, "sizeBytes", void 0);
|
|
295
|
+
/** The pot's fields on campaign create (and on a move); flat for the reason given in agency-campaigns.ts. */
|
|
296
|
+
class PotFields {
|
|
297
|
+
assignPot(data) {
|
|
298
|
+
this.potBudgetMinor = data?.potBudgetMinor;
|
|
299
|
+
this.potMaxBudgetMinor = data?.potMaxBudgetMinor;
|
|
300
|
+
this.potCapMinor = data?.potCapMinor;
|
|
301
|
+
this.potBonusWindowDays = data?.potBonusWindowDays;
|
|
302
|
+
this.potKeepUpMonths = data?.potKeepUpMonths;
|
|
303
|
+
this.potGoalViews = data?.potGoalViews;
|
|
304
|
+
this.potCurrency = data?.potCurrency;
|
|
305
|
+
this.potApplicationDeadlineAt = data?.potApplicationDeadlineAt;
|
|
306
|
+
this.potPaidPartnershipLabel = data?.potPaidPartnershipLabel;
|
|
307
|
+
this.potSponsorHandle = data?.potSponsorHandle;
|
|
308
|
+
this.potCollabHandle = data?.potCollabHandle;
|
|
309
|
+
this.potFirstComment = data?.potFirstComment;
|
|
310
|
+
this.potCommentsEnabled = data?.potCommentsEnabled;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
exports.PotFields = PotFields;
|
|
314
|
+
__decorate([
|
|
315
|
+
(0, class_validator_1.IsOptional)(),
|
|
316
|
+
(0, class_validator_1.IsInt)(),
|
|
317
|
+
(0, class_validator_1.Min)(1),
|
|
318
|
+
(0, class_validator_1.Max)(MONEY_MAX),
|
|
319
|
+
__metadata("design:type", Number)
|
|
320
|
+
], PotFields.prototype, "potBudgetMinor", void 0);
|
|
321
|
+
__decorate([
|
|
322
|
+
(0, class_validator_1.IsOptional)(),
|
|
323
|
+
(0, class_validator_1.IsInt)(),
|
|
324
|
+
(0, class_validator_1.Min)(1),
|
|
325
|
+
(0, class_validator_1.Max)(MONEY_MAX),
|
|
326
|
+
__metadata("design:type", Number)
|
|
327
|
+
], PotFields.prototype, "potMaxBudgetMinor", void 0);
|
|
328
|
+
__decorate([
|
|
329
|
+
(0, class_validator_1.IsOptional)(),
|
|
330
|
+
(0, class_validator_1.IsInt)(),
|
|
331
|
+
(0, class_validator_1.Min)(1),
|
|
332
|
+
(0, class_validator_1.Max)(MONEY_MAX),
|
|
333
|
+
__metadata("design:type", Number)
|
|
334
|
+
], PotFields.prototype, "potCapMinor", void 0);
|
|
335
|
+
__decorate([
|
|
336
|
+
(0, class_validator_1.IsOptional)(),
|
|
337
|
+
(0, class_validator_1.IsInt)(),
|
|
338
|
+
(0, class_validator_1.Min)(1),
|
|
339
|
+
(0, class_validator_1.Max)(365),
|
|
340
|
+
__metadata("design:type", Number)
|
|
341
|
+
], PotFields.prototype, "potBonusWindowDays", void 0);
|
|
342
|
+
__decorate([
|
|
343
|
+
(0, class_validator_1.IsOptional)(),
|
|
344
|
+
(0, class_validator_1.IsInt)(),
|
|
345
|
+
(0, class_validator_1.Min)(1),
|
|
346
|
+
(0, class_validator_1.Max)(60),
|
|
347
|
+
__metadata("design:type", Number)
|
|
348
|
+
], PotFields.prototype, "potKeepUpMonths", void 0);
|
|
349
|
+
__decorate([
|
|
350
|
+
(0, class_validator_1.IsOptional)(),
|
|
351
|
+
(0, class_validator_1.IsInt)(),
|
|
352
|
+
(0, class_validator_1.Min)(1),
|
|
353
|
+
(0, class_validator_1.Max)(100000000000),
|
|
354
|
+
__metadata("design:type", Number)
|
|
355
|
+
], PotFields.prototype, "potGoalViews", void 0);
|
|
356
|
+
__decorate([
|
|
357
|
+
(0, class_validator_1.IsOptional)(),
|
|
358
|
+
(0, class_validator_1.IsIn)(exports.POT_CURRENCIES),
|
|
359
|
+
__metadata("design:type", String)
|
|
360
|
+
], PotFields.prototype, "potCurrency", void 0);
|
|
361
|
+
__decorate([
|
|
362
|
+
(0, class_validator_1.IsOptional)(),
|
|
363
|
+
(0, class_validator_1.IsDateString)(),
|
|
364
|
+
__metadata("design:type", String)
|
|
365
|
+
], PotFields.prototype, "potApplicationDeadlineAt", void 0);
|
|
366
|
+
__decorate([
|
|
367
|
+
(0, class_validator_1.IsOptional)(),
|
|
368
|
+
(0, class_validator_1.IsBoolean)(),
|
|
369
|
+
__metadata("design:type", Boolean)
|
|
370
|
+
], PotFields.prototype, "potPaidPartnershipLabel", void 0);
|
|
371
|
+
__decorate([
|
|
372
|
+
(0, class_validator_1.IsOptional)(),
|
|
373
|
+
(0, class_validator_1.IsString)(),
|
|
374
|
+
(0, class_validator_1.Matches)(/^@?[A-Za-z0-9._]{1,64}$/),
|
|
375
|
+
__metadata("design:type", String)
|
|
376
|
+
], PotFields.prototype, "potSponsorHandle", void 0);
|
|
377
|
+
__decorate([
|
|
378
|
+
(0, class_validator_1.IsOptional)(),
|
|
379
|
+
(0, class_validator_1.IsString)(),
|
|
380
|
+
(0, class_validator_1.Matches)(/^@?[A-Za-z0-9._]{1,64}$/),
|
|
381
|
+
__metadata("design:type", String)
|
|
382
|
+
], PotFields.prototype, "potCollabHandle", void 0);
|
|
383
|
+
__decorate([
|
|
384
|
+
(0, class_validator_1.IsOptional)(),
|
|
385
|
+
(0, class_validator_1.IsString)(),
|
|
386
|
+
(0, class_validator_1.MaxLength)(2200),
|
|
387
|
+
__metadata("design:type", String)
|
|
388
|
+
], PotFields.prototype, "potFirstComment", void 0);
|
|
389
|
+
__decorate([
|
|
390
|
+
(0, class_validator_1.IsOptional)(),
|
|
391
|
+
(0, class_validator_1.IsBoolean)(),
|
|
392
|
+
__metadata("design:type", Boolean)
|
|
393
|
+
], PotFields.prototype, "potCommentsEnabled", void 0);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CampaignSide, I_CampaignCreatorIdentity } from './agency-campaigns';
|
|
2
|
+
/**
|
|
3
|
+
* Agency ↔ creator threads (spec 175 US8, FR-023): one conversation per
|
|
4
|
+
* organization and creator, inside ad2app.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately NOT the creator's platform inbox: nothing here reaches a social
|
|
7
|
+
* platform, and no grant scope opens a thread (SC-015). A thread is the two
|
|
8
|
+
* parties' own; a grant is a read of the creator's numbers, and the two never
|
|
9
|
+
* meet.
|
|
10
|
+
*
|
|
11
|
+
* "Unanswered" is derived: the other side wrote last. There is no read state
|
|
12
|
+
* and no mark-read step.
|
|
13
|
+
*/
|
|
14
|
+
export declare const AGENCY_THREAD_CODES: {
|
|
15
|
+
/** No live relationship any more: the history stays readable, nothing can be sent (FR-023). */
|
|
16
|
+
readonly CLOSED: "agency-thread-closed";
|
|
17
|
+
/** Too many messages from one person in the window (THREAD_POST_LIMIT). */
|
|
18
|
+
readonly RATE_LIMITED: "agency-thread-rate-limited";
|
|
19
|
+
};
|
|
20
|
+
/** Messages one person may post across all their threads per window: a conversation pace, never a script's. */
|
|
21
|
+
export declare const THREAD_POST_LIMIT: {
|
|
22
|
+
readonly limit: 60;
|
|
23
|
+
readonly windowMinutes: 10;
|
|
24
|
+
};
|
|
25
|
+
/** Long enough for a real message, short enough to stay a message. */
|
|
26
|
+
export declare const THREAD_MESSAGE_MAX = 4000;
|
|
27
|
+
/** Messages per read: the newest page with the thread, earlier pages on request. */
|
|
28
|
+
export declare const THREAD_PAGE_SIZE = 100;
|
|
29
|
+
export interface I_AgencyThreadMessage {
|
|
30
|
+
id: string;
|
|
31
|
+
side: CampaignSide;
|
|
32
|
+
body: string;
|
|
33
|
+
at: string;
|
|
34
|
+
}
|
|
35
|
+
export interface I_AgencyThreadSummary {
|
|
36
|
+
id: string;
|
|
37
|
+
/** Agency side: who the thread is with. */
|
|
38
|
+
creator: I_CampaignCreatorIdentity;
|
|
39
|
+
/** Creator side: which organization. */
|
|
40
|
+
organization: {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
lastMessage: {
|
|
45
|
+
side: CampaignSide;
|
|
46
|
+
body: string;
|
|
47
|
+
at: string;
|
|
48
|
+
} | null;
|
|
49
|
+
/** The other side wrote last, seen from the caller's side. */
|
|
50
|
+
unanswered: boolean;
|
|
51
|
+
/** No live relationship: read-only for whoever still exists. */
|
|
52
|
+
closed: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface I_AgencyThread {
|
|
55
|
+
summary: I_AgencyThreadSummary;
|
|
56
|
+
/** The newest page, oldest first. */
|
|
57
|
+
messages: I_AgencyThreadMessage[];
|
|
58
|
+
/** Older messages exist: read them with `…/messages?before=<oldest id>`. */
|
|
59
|
+
hasEarlier: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** `GET …/threads/:id/messages?before=<message id>` — the page before a message. */
|
|
62
|
+
export interface I_AgencyThreadPage {
|
|
63
|
+
/** Oldest first. */
|
|
64
|
+
messages: I_AgencyThreadMessage[];
|
|
65
|
+
hasEarlier: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** `GET /agency/threads` — the agency's inbox. */
|
|
68
|
+
export interface I_AgencyThreads {
|
|
69
|
+
items: I_AgencyThreadSummary[];
|
|
70
|
+
/** Creators on the roster the agency has no open thread with yet. */
|
|
71
|
+
startable: I_CampaignCreatorIdentity[];
|
|
72
|
+
/** The Inbox rail count. */
|
|
73
|
+
unanswered: number;
|
|
74
|
+
}
|
|
75
|
+
/** `GET /agency/creator-threads/mine` — the creator's threads, in the Agency tab. */
|
|
76
|
+
export interface I_CreatorThreads {
|
|
77
|
+
items: I_AgencyThreadSummary[];
|
|
78
|
+
unanswered: number;
|
|
79
|
+
}
|
|
80
|
+
export declare class StartAgencyThreadDto {
|
|
81
|
+
creatorUserId: string;
|
|
82
|
+
constructor(data?: Partial<StartAgencyThreadDto>);
|
|
83
|
+
}
|
|
84
|
+
export declare class PostThreadMessageDto {
|
|
85
|
+
text: string;
|
|
86
|
+
constructor(data?: Partial<PostThreadMessageDto>);
|
|
87
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.PostThreadMessageDto = exports.StartAgencyThreadDto = exports.THREAD_PAGE_SIZE = exports.THREAD_MESSAGE_MAX = exports.THREAD_POST_LIMIT = exports.AGENCY_THREAD_CODES = void 0;
|
|
13
|
+
const class_validator_1 = require("class-validator");
|
|
14
|
+
/**
|
|
15
|
+
* Agency ↔ creator threads (spec 175 US8, FR-023): one conversation per
|
|
16
|
+
* organization and creator, inside ad2app.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately NOT the creator's platform inbox: nothing here reaches a social
|
|
19
|
+
* platform, and no grant scope opens a thread (SC-015). A thread is the two
|
|
20
|
+
* parties' own; a grant is a read of the creator's numbers, and the two never
|
|
21
|
+
* meet.
|
|
22
|
+
*
|
|
23
|
+
* "Unanswered" is derived: the other side wrote last. There is no read state
|
|
24
|
+
* and no mark-read step.
|
|
25
|
+
*/
|
|
26
|
+
exports.AGENCY_THREAD_CODES = {
|
|
27
|
+
/** No live relationship any more: the history stays readable, nothing can be sent (FR-023). */
|
|
28
|
+
CLOSED: 'agency-thread-closed',
|
|
29
|
+
/** Too many messages from one person in the window (THREAD_POST_LIMIT). */
|
|
30
|
+
RATE_LIMITED: 'agency-thread-rate-limited',
|
|
31
|
+
};
|
|
32
|
+
/** Messages one person may post across all their threads per window: a conversation pace, never a script's. */
|
|
33
|
+
exports.THREAD_POST_LIMIT = { limit: 60, windowMinutes: 10 };
|
|
34
|
+
/** Long enough for a real message, short enough to stay a message. */
|
|
35
|
+
exports.THREAD_MESSAGE_MAX = 4000;
|
|
36
|
+
/** Messages per read: the newest page with the thread, earlier pages on request. */
|
|
37
|
+
exports.THREAD_PAGE_SIZE = 100;
|
|
38
|
+
class StartAgencyThreadDto {
|
|
39
|
+
constructor(data) {
|
|
40
|
+
this.creatorUserId = data?.creatorUserId ?? '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.StartAgencyThreadDto = StartAgencyThreadDto;
|
|
44
|
+
__decorate([
|
|
45
|
+
(0, class_validator_1.IsString)(),
|
|
46
|
+
(0, class_validator_1.Matches)(/^[A-Za-z0-9-]{1,128}$/),
|
|
47
|
+
__metadata("design:type", String)
|
|
48
|
+
], StartAgencyThreadDto.prototype, "creatorUserId", void 0);
|
|
49
|
+
class PostThreadMessageDto {
|
|
50
|
+
constructor(data) {
|
|
51
|
+
this.text = data?.text ?? '';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.PostThreadMessageDto = PostThreadMessageDto;
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, class_validator_1.IsString)(),
|
|
57
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
58
|
+
(0, class_validator_1.MaxLength)(exports.THREAD_MESSAGE_MAX),
|
|
59
|
+
__metadata("design:type", String)
|
|
60
|
+
], PostThreadMessageDto.prototype, "text", void 0);
|
package/dist/types/agency.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export declare const AGENCY_ROUTE_PATHS: {
|
|
|
33
33
|
readonly join: "/agency/join";
|
|
34
34
|
/** A creator answering an access request (FR-016). */
|
|
35
35
|
readonly requests: "/agency/requests";
|
|
36
|
+
/** A creator's campaign invitations and campaigns: the Agency tab of their Inbox (FR-023, FR-026). */
|
|
37
|
+
readonly creatorCampaigns: "/inbox/agency";
|
|
36
38
|
};
|
|
37
39
|
/**
|
|
38
40
|
* The complete set of reads a grant may carry. A CEILING, not a starting point.
|
package/dist/types/agency.js
CHANGED
|
@@ -32,6 +32,8 @@ exports.AGENCY_ROUTE_PATHS = {
|
|
|
32
32
|
join: '/agency/join',
|
|
33
33
|
/** A creator answering an access request (FR-016). */
|
|
34
34
|
requests: '/agency/requests',
|
|
35
|
+
/** A creator's campaign invitations and campaigns: the Agency tab of their Inbox (FR-023, FR-026). */
|
|
36
|
+
creatorCampaigns: '/inbox/agency',
|
|
35
37
|
};
|
|
36
38
|
// ── Scopes ───────────────────────────────────────────────────────────────────
|
|
37
39
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -38,4 +38,7 @@ export * from "./I_SM_Platform";
|
|
|
38
38
|
export * from "./scheduling";
|
|
39
39
|
export * from "./agent";
|
|
40
40
|
export * from "./agency";
|
|
41
|
+
export * from "./agency-campaigns";
|
|
42
|
+
export * from "./agency-pots";
|
|
43
|
+
export * from "./agency-threads";
|
|
41
44
|
export * from "./I_AccessDenial";
|
package/dist/types/index.js
CHANGED
|
@@ -57,5 +57,8 @@ __exportStar(require("./scheduling"), exports);
|
|
|
57
57
|
__exportStar(require("./agent"), exports);
|
|
58
58
|
// ── Agency domain (organizations, memberships, creator access grants, spec 175)
|
|
59
59
|
__exportStar(require("./agency"), exports);
|
|
60
|
+
__exportStar(require("./agency-campaigns"), exports);
|
|
61
|
+
__exportStar(require("./agency-pots"), exports);
|
|
62
|
+
__exportStar(require("./agency-threads"), exports);
|
|
60
63
|
// ── Access control ────────────────────────────────────────────────────────────
|
|
61
64
|
__exportStar(require("./I_AccessDenial"), exports);
|
package/package.json
CHANGED
|
@@ -196,11 +196,62 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
|
|
|
196
196
|
actor_role: "member",
|
|
197
197
|
origin: "agency_request",
|
|
198
198
|
},
|
|
199
|
+
[EVENTS.AGENCY_CAMPAIGN_CREATED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
|
|
200
|
+
[EVENTS.AGENCY_CAMPAIGN_CREATOR_INVITED]: {
|
|
201
|
+
organization_id: "org-1",
|
|
202
|
+
actor_role: "member",
|
|
203
|
+
campaign_id: "c-1",
|
|
204
|
+
creator_user_id: "u-1",
|
|
205
|
+
with_access_request: true,
|
|
206
|
+
},
|
|
207
|
+
[EVENTS.AGENCY_CAMPAIGN_JOINED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
|
|
208
|
+
[EVENTS.AGENCY_CAMPAIGN_DECLINED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
|
|
209
|
+
[EVENTS.AGENCY_CAMPAIGN_LEFT]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
|
|
210
|
+
[EVENTS.AGENCY_CAMPAIGN_CREATOR_REMOVED]: {
|
|
211
|
+
organization_id: "org-1",
|
|
212
|
+
actor_role: "owner",
|
|
213
|
+
campaign_id: "c-1",
|
|
214
|
+
creator_user_id: "u-1",
|
|
215
|
+
},
|
|
216
|
+
[EVENTS.AGENCY_CAMPAIGN_OFFER_SENT]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
|
|
217
|
+
[EVENTS.AGENCY_CAMPAIGN_OFFER_ACTED]: {
|
|
218
|
+
organization_id: "org-1",
|
|
219
|
+
actor_role: "creator",
|
|
220
|
+
campaign_id: "c-1",
|
|
221
|
+
action: "counter",
|
|
222
|
+
},
|
|
223
|
+
[EVENTS.AGENCY_CAMPAIGN_MATERIAL_UPLOADED]: {
|
|
224
|
+
organization_id: "org-1",
|
|
225
|
+
actor_role: "creator",
|
|
226
|
+
campaign_id: "c-1",
|
|
227
|
+
version: 2,
|
|
228
|
+
},
|
|
229
|
+
[EVENTS.AGENCY_CAMPAIGN_MATERIAL_REVIEWED]: {
|
|
230
|
+
organization_id: "org-1",
|
|
231
|
+
actor_role: "member",
|
|
232
|
+
campaign_id: "c-1",
|
|
233
|
+
action: "request_changes",
|
|
234
|
+
},
|
|
235
|
+
[EVENTS.AGENCY_CAMPAIGN_PUBLISHED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
|
|
236
|
+
[EVENTS.AGENCY_CAMPAIGN_MILESTONE_CONFIRMED]: { organization_id: "org-1", actor_role: "owner", campaign_id: "c-1" },
|
|
237
|
+
[EVENTS.AGENCY_THREAD_STARTED]: { organization_id: "org-1", actor_role: "member", thread_id: "t-1" },
|
|
238
|
+
[EVENTS.AGENCY_THREAD_MESSAGE_SENT]: { organization_id: "org-1", actor_role: "creator", thread_id: "t-1" },
|
|
239
|
+
[EVENTS.AGENCY_POT_APPLIED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
|
|
240
|
+
[EVENTS.AGENCY_POT_APPLICATION_DECIDED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1", action: "accept" },
|
|
241
|
+
[EVENTS.AGENCY_POT_REFILLED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
|
|
242
|
+
[EVENTS.AGENCY_POT_MOVED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
|
|
243
|
+
[EVENTS.AGENCY_POT_PUBLISH_TIME_CONFIRMED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
|
|
244
|
+
[EVENTS.AGENCY_POT_PUBLISHED]: { organization_id: "org-1", actor_role: "creator", campaign_id: "c-1" },
|
|
245
|
+
[EVENTS.AGENCY_POT_BONUS_HELD]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1", reason: "jump" },
|
|
246
|
+
[EVENTS.AGENCY_POT_BONUS_DECIDED]: { organization_id: "org-1", actor_role: "owner", campaign_id: "c-1", action: "cap" },
|
|
247
|
+
[EVENTS.AGENCY_POT_REMOVAL_DECIDED]: { organization_id: "org-1", actor_role: "owner", campaign_id: "c-1", decision: "withhold_base" },
|
|
248
|
+
[EVENTS.AGENCY_POT_EXPORTED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1", format: "xlsx" },
|
|
249
|
+
[EVENTS.AGENCY_POT_FILE_UPLOADED]: { organization_id: "org-1", actor_role: "member", campaign_id: "c-1" },
|
|
199
250
|
};
|
|
200
251
|
|
|
201
252
|
test("no agency event may carry the discover query — only whether it matched (FR-015)", () => {
|
|
202
253
|
const agencyEvents = Object.values(EVENTS).filter((name) => name.startsWith("agency_"));
|
|
203
|
-
assert.equal(agencyEvents.length,
|
|
254
|
+
assert.equal(agencyEvents.length, 40, "the 175 set is 15 roster events, 12 campaign-loop events (US7), 2 thread events (US8) and 11 pot events (US9)");
|
|
204
255
|
for (const name of agencyEvents) {
|
|
205
256
|
const props = EVENT_PROPERTY_WITNESS[name as keyof typeof EVENT_PROPERTY_WITNESS];
|
|
206
257
|
for (const key of Object.keys(props)) {
|