ad2app-lib 1.49.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.
@@ -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);
@@ -39,5 +39,6 @@ export * from "./scheduling";
39
39
  export * from "./agent";
40
40
  export * from "./agency";
41
41
  export * from "./agency-campaigns";
42
+ export * from "./agency-pots";
42
43
  export * from "./agency-threads";
43
44
  export * from "./I_AccessDenial";
@@ -58,6 +58,7 @@ __exportStar(require("./agent"), exports);
58
58
  // ── Agency domain (organizations, memberships, creator access grants, spec 175)
59
59
  __exportStar(require("./agency"), exports);
60
60
  __exportStar(require("./agency-campaigns"), exports);
61
+ __exportStar(require("./agency-pots"), exports);
61
62
  __exportStar(require("./agency-threads"), exports);
62
63
  // ── Access control ────────────────────────────────────────────────────────────
63
64
  __exportStar(require("./I_AccessDenial"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.49.0",
3
+ "version": "1.50.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",
@@ -236,11 +236,22 @@ const EVENT_PROPERTY_WITNESS: { [E in keyof EventProperties]: EventProperties[E]
236
236
  [EVENTS.AGENCY_CAMPAIGN_MILESTONE_CONFIRMED]: { organization_id: "org-1", actor_role: "owner", campaign_id: "c-1" },
237
237
  [EVENTS.AGENCY_THREAD_STARTED]: { organization_id: "org-1", actor_role: "member", thread_id: "t-1" },
238
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" },
239
250
  };
240
251
 
241
252
  test("no agency event may carry the discover query — only whether it matched (FR-015)", () => {
242
253
  const agencyEvents = Object.values(EVENTS).filter((name) => name.startsWith("agency_"));
243
- assert.equal(agencyEvents.length, 29, "the 175 set is 15 roster events, 12 campaign-loop events (US7) and 2 thread events (US8)");
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)");
244
255
  for (const name of agencyEvents) {
245
256
  const props = EVENT_PROPERTY_WITNESS[name as keyof typeof EVENT_PROPERTY_WITNESS];
246
257
  for (const key of Object.keys(props)) {
@@ -208,6 +208,18 @@ export const EVENTS = {
208
208
  // Threads (175 US8). Never the message body: a conversation is not analytics.
209
209
  AGENCY_THREAD_STARTED: 'agency_thread_started',
210
210
  AGENCY_THREAD_MESSAGE_SENT: 'agency_thread_message_sent',
211
+ // Pot campaigns (175 US9). Never an amount: the pot's economics are the agency's.
212
+ AGENCY_POT_APPLIED: 'agency_pot_applied',
213
+ AGENCY_POT_APPLICATION_DECIDED: 'agency_pot_application_decided',
214
+ AGENCY_POT_REFILLED: 'agency_pot_refilled',
215
+ AGENCY_POT_MOVED: 'agency_pot_moved',
216
+ AGENCY_POT_PUBLISH_TIME_CONFIRMED: 'agency_pot_publish_time_confirmed',
217
+ AGENCY_POT_PUBLISHED: 'agency_pot_published',
218
+ AGENCY_POT_BONUS_HELD: 'agency_pot_bonus_held',
219
+ AGENCY_POT_BONUS_DECIDED: 'agency_pot_bonus_decided',
220
+ AGENCY_POT_REMOVAL_DECIDED: 'agency_pot_removal_decided',
221
+ AGENCY_POT_EXPORTED: 'agency_pot_exported',
222
+ AGENCY_POT_FILE_UPLOADED: 'agency_pot_file_uploaded',
211
223
  } as const;
212
224
 
213
225
  export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
@@ -714,6 +726,17 @@ export interface EventProperties {
714
726
  // Threads (175 US8): which thread, never its content.
715
727
  [EVENTS.AGENCY_THREAD_STARTED]: AgencyEventProperties & { thread_id: string };
716
728
  [EVENTS.AGENCY_THREAD_MESSAGE_SENT]: AgencyEventProperties & { thread_id: string };
729
+ [EVENTS.AGENCY_POT_APPLIED]: AgencyCampaignEventProperties;
730
+ [EVENTS.AGENCY_POT_APPLICATION_DECIDED]: AgencyCampaignEventProperties & { action: 'accept' | 'decline' };
731
+ [EVENTS.AGENCY_POT_REFILLED]: AgencyCampaignEventProperties;
732
+ [EVENTS.AGENCY_POT_MOVED]: AgencyCampaignEventProperties;
733
+ [EVENTS.AGENCY_POT_PUBLISH_TIME_CONFIRMED]: AgencyCampaignEventProperties;
734
+ [EVENTS.AGENCY_POT_PUBLISHED]: AgencyCampaignEventProperties;
735
+ [EVENTS.AGENCY_POT_BONUS_HELD]: AgencyCampaignEventProperties & { reason: 'jump' | 'paid_delivery' };
736
+ [EVENTS.AGENCY_POT_BONUS_DECIDED]: AgencyCampaignEventProperties & { action: 'approve' | 'cap' | 'message' };
737
+ [EVENTS.AGENCY_POT_REMOVAL_DECIDED]: AgencyCampaignEventProperties & { decision: 'keep_base' | 'withhold_base' };
738
+ [EVENTS.AGENCY_POT_EXPORTED]: AgencyCampaignEventProperties & { format: 'csv' | 'xlsx' | 'report' };
739
+ [EVENTS.AGENCY_POT_FILE_UPLOADED]: AgencyCampaignEventProperties;
717
740
  }
718
741
 
719
742
  /** Canonical person property keys (set via identify / $set). */
@@ -17,6 +17,7 @@ import {
17
17
  import { I_MediaStage } from './I_Media';
18
18
  import { I_OfferStatus } from './I_Offer';
19
19
  import { AGENCY_GRANT_SCOPES, type I_AgencyGrantScope } from './agency';
20
+ import { PotFields, type I_AgencyPotView, type I_CampaignFile, type I_CreatorPotView, type I_DisclosureCheck } from './agency-pots';
20
21
 
21
22
  /**
22
23
  * The campaign loop (spec 175 US7) — an organization runs a campaign with the
@@ -68,6 +69,14 @@ export type SettlementForm = (typeof SETTLEMENT_FORMS)[number];
68
69
 
69
70
  // ── States ───────────────────────────────────────────────────────────────────
70
71
 
72
+ /**
73
+ * An offer campaign negotiates one price per creator (US7); a pot campaign pays
74
+ * from one pot on each creator's own terms (US9). Chosen at creation, fixed once
75
+ * the first creator is invited (FR-030).
76
+ */
77
+ export const AGENCY_CAMPAIGN_KINDS = ['offer', 'pot'] as const;
78
+ export type AgencyCampaignKind = (typeof AGENCY_CAMPAIGN_KINDS)[number];
79
+
71
80
  export const AGENCY_CAMPAIGN_MEMBER_STATES = ['invited', 'joined', 'declined', 'left', 'removed'] as const;
72
81
  export type AgencyCampaignMemberState = (typeof AGENCY_CAMPAIGN_MEMBER_STATES)[number];
73
82
 
@@ -143,6 +152,32 @@ export const AGENCY_CAMPAIGN_EVENT_KINDS = [
143
152
  'material_rejected',
144
153
  'published',
145
154
  'milestone_confirmed',
155
+ // US9: the pot. Every money act carries its amount (FR-021, FR-033).
156
+ 'pot_applied',
157
+ 'pot_application_withdrawn',
158
+ 'pot_accepted',
159
+ 'pot_declined',
160
+ 'pot_released',
161
+ 'pot_refilled',
162
+ 'pot_moved',
163
+ 'pot_deadline_moved',
164
+ 'pot_publish_time_set',
165
+ 'pot_publish_time_confirmed',
166
+ /** Zernio refused the post at scheduling; `note` carries its reason. */
167
+ 'pot_publish_refused',
168
+ 'pot_published',
169
+ 'pot_counted',
170
+ 'pot_frozen',
171
+ 'pot_bonus_held',
172
+ 'pot_bonus_approved',
173
+ 'pot_bonus_capped',
174
+ 'pot_bonus_message',
175
+ 'pot_kept',
176
+ 'pot_broken',
177
+ 'pot_removal_decided',
178
+ 'caption_edited',
179
+ 'file_uploaded',
180
+ 'file_removed',
146
181
  ] as const;
147
182
  export type AgencyCampaignEventKind = (typeof AGENCY_CAMPAIGN_EVENT_KINDS)[number];
148
183
 
@@ -214,6 +249,10 @@ export interface I_AgencyMaterial {
214
249
  uploadedAt: string;
215
250
  /** The note sent with a change request or a rejection of THIS version. */
216
251
  note: string | null;
252
+ /** The post's caption, uploaded with the material (US9, FR-032); the creator may edit it until publication. Optional: 1.49 backends omit it. */
253
+ caption?: string | null;
254
+ /** `checkDisclosure(caption)`, returned with every read (FR-040); null without a caption. Optional: 1.49 backends omit it. */
255
+ disclosure?: I_DisclosureCheck | null;
217
256
  }
218
257
 
219
258
  export interface I_AgencyMilestone {
@@ -233,8 +272,12 @@ export interface I_AgencyMilestone {
233
272
  export interface I_AgencyCampaignEvent {
234
273
  kind: AgencyCampaignEventKind;
235
274
  side: CampaignSide;
275
+ /** Done by ad2app itself (the daily pot job), not by a person on that side. */
276
+ byAd2app?: true;
236
277
  at: string;
237
278
  price?: I_Money;
279
+ /** A pot act's amount: a refill, a move, a reserved guarantee, an accrued bonus. */
280
+ amount?: I_Money;
238
281
  note?: string;
239
282
  }
240
283
 
@@ -279,6 +322,8 @@ export interface I_AgencyCampaignPipeline {
279
322
  export interface I_AgencyCampaignCard {
280
323
  id: string;
281
324
  name: string;
325
+ /** Absent from 1.49 backends: read as 'offer'. */
326
+ kind?: AgencyCampaignKind;
282
327
  isDraft: boolean;
283
328
  liveByAt: string | null;
284
329
  draftDueAt: string | null;
@@ -291,6 +336,10 @@ export interface I_AgencyCampaignCard {
291
336
  export interface I_AgencyCampaign {
292
337
  id: string;
293
338
  name: string;
339
+ /** Absent from 1.49 backends: read as 'offer'. */
340
+ kind?: AgencyCampaignKind;
341
+ /** Set on a pot campaign; the pot board itself is `GET /agency/campaigns/:id/pot/board`. */
342
+ pot?: I_AgencyPotView | null;
294
343
  isDraft: boolean;
295
344
  brief: I_AgencyCampaignBrief;
296
345
  members: I_AgencyCampaignMemberRow[];
@@ -313,6 +362,8 @@ export interface I_AgencyCampaignAttention {
313
362
  counterOffers: number;
314
363
  materialsToReview: number;
315
364
  milestonesReady: number;
365
+ /** US9: applications to answer, held bonuses to decide, and posts taken down after the bonus window. */
366
+ potDecisions?: number;
316
367
  total: number;
317
368
  }
318
369
 
@@ -348,7 +399,12 @@ export interface I_CreatorCampaignView {
348
399
  memberId: string;
349
400
  state: AgencyCampaignMemberState;
350
401
  organization: { id: string; name: string };
351
- campaign: { id: string; name: string; brief: I_AgencyCampaignBrief };
402
+ /** `kind` is absent from 1.49 backends: read as 'offer'. */
403
+ campaign: { id: string; name: string; brief: I_AgencyCampaignBrief; kind?: AgencyCampaignKind };
404
+ /** Set on a pot campaign: the creator's own terms, earnings and the two-valued availability, nothing pot-level (SC-016). */
405
+ pot?: I_CreatorPotView | null;
406
+ /** The agency's files for the campaign (FR-039): every one is for its creators; opened through a signed read. */
407
+ files?: I_CampaignFile[];
352
408
  creatorBrief: string | null;
353
409
  /** The access request folded into the invitation, while it is open. */
354
410
  requestedScopes: I_AgencyGrantScope[];
@@ -390,8 +446,8 @@ export interface I_SignedRead {
390
446
  // never reads. `deliverables` is an array of plain objects checked item by
391
447
  // item in the service against CAMPAIGN_PLACEMENTS — the pair is the rule.
392
448
 
393
- /** The brief's fields, shared by create and update. All optional on the wire. */
394
- class AgencyCampaignBriefFields {
449
+ /** The brief's fields, shared by create and update. All optional on the wire. The pot's fields ride along (US9). */
450
+ class AgencyCampaignBriefFields extends PotFields {
395
451
  @IsOptional()
396
452
  @IsString()
397
453
  @MaxLength(10000)
@@ -429,6 +485,7 @@ class AgencyCampaignBriefFields {
429
485
  revisionRounds?: number | null;
430
486
 
431
487
  protected assignBrief(data?: Partial<AgencyCampaignBriefFields>): void {
488
+ this.assignPot(data);
432
489
  this.text = data?.text;
433
490
  this.deliverables = data?.deliverables;
434
491
  this.draftDueAt = data?.draftDueAt;
@@ -445,9 +502,15 @@ export class CreateAgencyCampaignDto extends AgencyCampaignBriefFields {
445
502
  @MaxLength(128)
446
503
  name: string;
447
504
 
505
+ /** Absent = an offer campaign (US7). */
506
+ @IsOptional()
507
+ @IsIn(AGENCY_CAMPAIGN_KINDS as unknown as string[])
508
+ kind?: AgencyCampaignKind;
509
+
448
510
  constructor(data?: Partial<CreateAgencyCampaignDto>) {
449
511
  super();
450
512
  this.name = data?.name ?? '';
513
+ this.kind = data?.kind;
451
514
  this.assignBrief(data);
452
515
  }
453
516
  }
@@ -464,10 +527,16 @@ export class UpdateAgencyCampaignDto extends AgencyCampaignBriefFields {
464
527
  @IsBoolean()
465
528
  isDraft?: boolean;
466
529
 
530
+ /** Switch offer ↔ pot; refused once the first creator is invited (FR-030). */
531
+ @IsOptional()
532
+ @IsIn(AGENCY_CAMPAIGN_KINDS as unknown as string[])
533
+ kind?: AgencyCampaignKind;
534
+
467
535
  constructor(data?: Partial<UpdateAgencyCampaignDto>) {
468
536
  super();
469
537
  this.name = data?.name;
470
538
  this.isDraft = data?.isDraft;
539
+ this.kind = data?.kind;
471
540
  this.assignBrief(data);
472
541
  }
473
542
  }
@@ -608,8 +677,15 @@ export class ConfirmMaterialUploadDto {
608
677
  @MaxLength(512)
609
678
  uploadId: string;
610
679
 
680
+ /** The post's caption, sent with the material on a pot campaign (FR-032). */
681
+ @IsOptional()
682
+ @IsString()
683
+ @MaxLength(2200)
684
+ caption?: string;
685
+
611
686
  constructor(data?: Partial<ConfirmMaterialUploadDto>) {
612
687
  this.uploadId = data?.uploadId ?? '';
688
+ this.caption = data?.caption;
613
689
  }
614
690
  }
615
691