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.
- package/dist/analytics/index.d.ts +32 -0
- package/dist/analytics/index.js +12 -0
- package/dist/types/agency-campaigns.d.ts +39 -3
- package/dist/types/agency-campaigns.js +56 -3
- package/dist/types/agency-pots.d.ts +518 -0
- package/dist/types/agency-pots.js +393 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/package.json +1 -1
- package/src/analytics/index.test.ts +12 -1
- package/src/analytics/index.ts +23 -0
- package/src/types/agency-campaigns.ts +79 -3
- package/src/types/agency-pots.test.ts +255 -0
- package/src/types/agency-pots.ts +751 -0
- package/src/types/index.ts +1 -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);
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -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
|
@@ -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,
|
|
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)) {
|
package/src/analytics/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|