ad2app-lib 1.44.1 → 1.49.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 +44 -0
- package/dist/analytics/index.js +17 -0
- package/dist/types/I_AccessDenial.d.ts +19 -0
- package/dist/types/I_AccessDenial.js +19 -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 +391 -0
- package/dist/types/agency-campaigns.js +426 -0
- package/dist/types/agency-threads.d.ts +87 -0
- package/dist/types/agency-threads.js +60 -0
- package/dist/types/agency.d.ts +250 -6
- package/dist/types/agency.js +54 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/scheduling/I_SchedulingProUpgrade.d.ts +10 -1
- package/dist/types/scheduling/I_SchedulingProUpgrade.js +4 -0
- package/package.json +2 -2
- package/src/analytics/index.test.ts +41 -1
- package/src/analytics/index.ts +39 -0
- package/src/types/I_AccessDenial.ts +19 -0
- package/src/types/I_Offer.ts +2 -0
- package/src/types/access-denial.test.ts +1 -1
- package/src/types/agency-billing.test.ts +83 -0
- package/src/types/agency-campaigns.test.ts +85 -0
- package/src/types/agency-campaigns.ts +663 -0
- package/src/types/agency-discover.test.ts +79 -0
- package/src/types/agency-threads.ts +103 -0
- package/src/types/agency.test.ts +21 -1
- package/src/types/agency.ts +261 -6
- package/src/types/index.ts +2 -0
- package/src/types/scheduling/I_SchedulingProUpgrade.ts +13 -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.
|
|
@@ -93,6 +95,12 @@ export interface I_AgencyMember {
|
|
|
93
95
|
/** Null while an e-mail invitation is outstanding — nobody has accepted yet. */
|
|
94
96
|
userId?: string;
|
|
95
97
|
invitedEmail: string;
|
|
98
|
+
/**
|
|
99
|
+
* The account's own e-mail once the invitation is accepted (and always for
|
|
100
|
+
* the owner, who applied rather than being invited). The Team panel names a
|
|
101
|
+
* person by this, never by the organization's contact address (T063).
|
|
102
|
+
*/
|
|
103
|
+
accountEmail?: string;
|
|
96
104
|
role: AgencyMemberRole;
|
|
97
105
|
invitedAt: string;
|
|
98
106
|
/** "Who is in" is exactly `acceptedAt != null`. */
|
|
@@ -152,7 +160,27 @@ export interface I_CreatorAccessGrant {
|
|
|
152
160
|
};
|
|
153
161
|
/** Which platforms the grant reaches right now — derived at read time, never stored. */
|
|
154
162
|
platformsReached: PublishPlatform[];
|
|
163
|
+
/**
|
|
164
|
+
* The agency's OPEN second ask on an active grant, for scopes the creator
|
|
165
|
+
* did not grant (FR-008). Kept apart from `requestedScopes`, so the consent
|
|
166
|
+
* screen names exactly this ask and nothing refused earlier (review H4).
|
|
167
|
+
* Absent when there is no open ask.
|
|
168
|
+
*/
|
|
169
|
+
reaskedScopes?: I_AgencyGrantScope[];
|
|
170
|
+
/** When that open ask expires unanswered. */
|
|
171
|
+
reaskExpiresAt?: string;
|
|
155
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Outcomes of discovery and requests that are not refusals of access, sent as
|
|
175
|
+
* codes so the screen composes the sentence in the reader's language (FR-019,
|
|
176
|
+
* review M4). A 429 carries `{ code, limit, windowMinutes }`.
|
|
177
|
+
*/
|
|
178
|
+
export declare const AGENCY_REQUEST_CODES: {
|
|
179
|
+
readonly RATE_LIMITED: "agency-rate-limited";
|
|
180
|
+
readonly REQUEST_EXPIRED: "agency-request-expired";
|
|
181
|
+
readonly REQUEST_ANSWERED: "agency-request-answered";
|
|
182
|
+
};
|
|
183
|
+
export type AgencyRequestCode = (typeof AGENCY_REQUEST_CODES)[keyof typeof AGENCY_REQUEST_CODES];
|
|
156
184
|
/**
|
|
157
185
|
* Roster row states, DECLARED IN PRECEDENCE ORDER (data-model §Derived).
|
|
158
186
|
*
|
|
@@ -160,7 +188,7 @@ export interface I_CreatorAccessGrant {
|
|
|
160
188
|
* first. `revoked` is deliberately absent — a revoked creator leaves the roster
|
|
161
189
|
* entirely for the settings history (FR-011).
|
|
162
190
|
*/
|
|
163
|
-
export declare const AGENCY_ROSTER_ROW_STATES: readonly ["requested", "lapsed", "disconnected", "stale", "active"];
|
|
191
|
+
export declare const AGENCY_ROSTER_ROW_STATES: readonly ["requested", "lapsed", "disconnected", "syncing", "stale", "active"];
|
|
164
192
|
export type AgencyRosterRowState = (typeof AGENCY_ROSTER_ROW_STATES)[number];
|
|
165
193
|
/**
|
|
166
194
|
* What "needs attention" means — ONE home for the KPI tile, the roster segment
|
|
@@ -171,6 +199,32 @@ export type AgencyRosterRowState = (typeof AGENCY_ROSTER_ROW_STATES)[number];
|
|
|
171
199
|
*/
|
|
172
200
|
export declare const AGENCY_ROSTER_NEEDS_ATTENTION_STATES: readonly ["lapsed", "disconnected", "stale"];
|
|
173
201
|
export type AgencyRosterNeedsAttentionState = (typeof AGENCY_ROSTER_NEEDS_ATTENTION_STATES)[number];
|
|
202
|
+
/**
|
|
203
|
+
* The rows that carry numbers — ONE home for "which creators' numbers may the
|
|
204
|
+
* agency see on the roster" (decided 2026-09-24, review M1/M2).
|
|
205
|
+
*
|
|
206
|
+
* Only a row whose card OPENS carries `metrics` and counts in the followers,
|
|
207
|
+
* followers-delta and comments totals: `active`, and `stale` (the card opens;
|
|
208
|
+
* the numbers are as old as `lastSyncAt` says). A `lapsed` creator's reads are
|
|
209
|
+
* refused on the card, so their numbers must not reach the agency through the
|
|
210
|
+
* roster instead; a `disconnected` creator has nothing live to count; a
|
|
211
|
+
* `requested` creator has shared nothing yet.
|
|
212
|
+
*/
|
|
213
|
+
export declare const AGENCY_ROSTER_METRICS_STATES: readonly ["syncing", "stale", "active"];
|
|
214
|
+
export type AgencyRosterMetricsState = (typeof AGENCY_ROSTER_METRICS_STATES)[number];
|
|
215
|
+
/**
|
|
216
|
+
* One creator's numbers on the roster (FR-011a: sortable metric columns), read
|
|
217
|
+
* from the daily-synced tables, never a vendor call. `null` is a REASON, never
|
|
218
|
+
* a zero: the creator did not grant that scope, or nothing has been synced yet.
|
|
219
|
+
*/
|
|
220
|
+
export interface I_AgencyRosterRowMetrics {
|
|
221
|
+
/** Latest followers over the creator's CONNECTED platforms; null without `analytics` or before the first sync. */
|
|
222
|
+
followers: number | null;
|
|
223
|
+
/** Against the snapshot seven days earlier, per connected platform; null without `analytics` or without a week-old point. */
|
|
224
|
+
followersDelta7d: number | null;
|
|
225
|
+
/** The current Monday-to-Sunday week, organization timezone; null without `comments`. */
|
|
226
|
+
commentsThisWeek: number | null;
|
|
227
|
+
}
|
|
174
228
|
export interface I_AgencyRosterRow {
|
|
175
229
|
grantId: string;
|
|
176
230
|
creator: {
|
|
@@ -180,22 +234,72 @@ export interface I_AgencyRosterRow {
|
|
|
180
234
|
platforms: PublishPlatform[];
|
|
181
235
|
state: AgencyRosterRowState;
|
|
182
236
|
grantedScopes: I_AgencyGrantScope[];
|
|
237
|
+
/**
|
|
238
|
+
* The newest successful daily sync. Absent on a `requested` row (nothing is
|
|
239
|
+
* shared yet, so there is nothing to have synced for the agency) and on a
|
|
240
|
+
* creator never synced. `platforms` stays on a requested row: it is what the
|
|
241
|
+
* consent screen names, a fact about the account rather than shared data.
|
|
242
|
+
*/
|
|
183
243
|
lastSyncAt?: string;
|
|
244
|
+
/**
|
|
245
|
+
* Present ONLY on rows in AGENCY_ROSTER_METRICS_STATES (`active`, `stale`):
|
|
246
|
+
* the rows whose card opens. Absent on `lapsed`, `disconnected` and
|
|
247
|
+
* `requested` rows — no numbers, not null numbers.
|
|
248
|
+
*/
|
|
249
|
+
metrics?: I_AgencyRosterRowMetrics;
|
|
250
|
+
/**
|
|
251
|
+
* The agency's own last "Ask again" (FR-008), while its one-per-TTL clock
|
|
252
|
+
* runs (audit F19): when it was sent and when another is possible. It says
|
|
253
|
+
* nothing about the ANSWER, because the clock runs whatever the creator did.
|
|
254
|
+
* The roster uses it to say "asked on …, again after …" instead of offering
|
|
255
|
+
* an ask that would not be sent. Absent once the clock has run out.
|
|
256
|
+
*/
|
|
257
|
+
reaskedAt?: string;
|
|
258
|
+
reaskAvailableAt?: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The platforms a creator has connected, as the agency side sees them
|
|
262
|
+
* (discovery, the grant's "reaches"). `GET users/me/connected-platforms`,
|
|
263
|
+
* answered to every creator, subscribed or not.
|
|
264
|
+
*/
|
|
265
|
+
export interface I_ConnectedPlatforms {
|
|
266
|
+
platforms: PublishPlatform[];
|
|
184
267
|
}
|
|
185
268
|
export interface I_AgencyRoster {
|
|
186
269
|
totals: {
|
|
187
270
|
creators: number;
|
|
188
271
|
active: number;
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Summed only over rows in AGENCY_ROSTER_METRICS_STATES whose creator holds
|
|
274
|
+
* `analytics`; disconnected platforms excluded. `null` when no counted
|
|
275
|
+
* creator has a synced number yet, never a zero standing in (T067).
|
|
276
|
+
*/
|
|
277
|
+
followers: number | null;
|
|
278
|
+
/**
|
|
279
|
+
* Same rows as `followers`, against the snapshot seven days earlier. `null`
|
|
280
|
+
* when no counted creator has a week-old point — unknown, never a zero.
|
|
281
|
+
*/
|
|
282
|
+
followersDelta7d: number | null;
|
|
283
|
+
/**
|
|
284
|
+
* Calendar week, Mon-Sun, organization timezone, over rows in
|
|
285
|
+
* AGENCY_ROSTER_METRICS_STATES whose creator holds `comments`. Like
|
|
286
|
+
* `followers`, `null` when no counted creator has a synced number yet:
|
|
287
|
+
* unknown, never a zero (SC-008, T067).
|
|
288
|
+
*/
|
|
289
|
+
commentsThisWeek: number | null;
|
|
194
290
|
/** Count of rows in AGENCY_ROSTER_NEEDS_ATTENTION_STATES. */
|
|
195
291
|
needsAttention: number;
|
|
196
292
|
};
|
|
197
293
|
items: I_AgencyRosterRow[];
|
|
198
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* A coverage that ENDED recently, so the creator meeting the paywall again is
|
|
297
|
+
* told why (FR-014: "an honest message, never a silent loss of access").
|
|
298
|
+
*/
|
|
299
|
+
export interface I_SponsorshipEnded {
|
|
300
|
+
organizationName: string;
|
|
301
|
+
endedAt: string;
|
|
302
|
+
}
|
|
199
303
|
/** What a sponsored creator sees on their OWN subscription screen (FR-014). */
|
|
200
304
|
export interface I_SponsoredBy {
|
|
201
305
|
organizationName: string;
|
|
@@ -203,6 +307,106 @@ export interface I_SponsoredBy {
|
|
|
203
307
|
/** Present once the sponsorship is ending: the date their plan runs to. */
|
|
204
308
|
until?: string;
|
|
205
309
|
}
|
|
310
|
+
/** The card on file, as Stripe describes it. Never more than these four facts. */
|
|
311
|
+
export interface I_AgencyPaymentMethod {
|
|
312
|
+
brand: string;
|
|
313
|
+
last4: string;
|
|
314
|
+
expMonth: number;
|
|
315
|
+
expYear: number;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* One invoice. `amount` is Stripe's minor unit verbatim; the display edge
|
|
319
|
+
* formats it once, like every other price in the app.
|
|
320
|
+
*/
|
|
321
|
+
export interface I_AgencyInvoice {
|
|
322
|
+
id: string;
|
|
323
|
+
number: string | null;
|
|
324
|
+
date: string;
|
|
325
|
+
amount: number;
|
|
326
|
+
currency: string;
|
|
327
|
+
status: string;
|
|
328
|
+
pdfUrl?: string;
|
|
329
|
+
hostedUrl?: string;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* One creator on an ACTIVE grant, with whether the agency covers their plan
|
|
333
|
+
* (FR-013b). `tier: null` is "not covered". `until` is set while a coverage is
|
|
334
|
+
* ending: it runs to the close of the period already paid for.
|
|
335
|
+
*/
|
|
336
|
+
export interface I_AgencyCoveredCreator {
|
|
337
|
+
grantId: string;
|
|
338
|
+
creator: {
|
|
339
|
+
id: string;
|
|
340
|
+
handle: string;
|
|
341
|
+
};
|
|
342
|
+
tier: SponsoredTier | null;
|
|
343
|
+
since?: string;
|
|
344
|
+
until?: string;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* An ended relationship, for the past-creators history (FR-013b). `creator.id`
|
|
348
|
+
* is null once the creator's account was purged; the handle is the snapshot
|
|
349
|
+
* frozen at grant time.
|
|
350
|
+
*/
|
|
351
|
+
export interface I_AgencyPastCreator {
|
|
352
|
+
grantId: string;
|
|
353
|
+
creator: {
|
|
354
|
+
id: string | null;
|
|
355
|
+
handle: string;
|
|
356
|
+
displayName?: string;
|
|
357
|
+
};
|
|
358
|
+
endedAt: string;
|
|
359
|
+
endedBy: CreatorAccessGrantRevokedBy;
|
|
360
|
+
coveredUntil?: string;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Everything the owner's billing section and panel show (FR-013, FR-013b).
|
|
364
|
+
*
|
|
365
|
+
* `seats` is the ONE home of the billed quantity: the count of active grants.
|
|
366
|
+
* Covering a creator never changes it (clarify 2026-09-17), so nothing here
|
|
367
|
+
* derives a price from `covered`.
|
|
368
|
+
*
|
|
369
|
+
* `price` is null when the seat product is not configured in this deployment
|
|
370
|
+
* (T031, the money gate); the screen says so rather than inventing a number.
|
|
371
|
+
* `nextInvoice` is Stripe's own preview, so a coupon is already applied to it.
|
|
372
|
+
*/
|
|
373
|
+
export interface I_AgencyBilling {
|
|
374
|
+
subscriptionStatus: AgencySubscriptionStatus;
|
|
375
|
+
/** Creators with an active grant. */
|
|
376
|
+
seats: number;
|
|
377
|
+
/**
|
|
378
|
+
* What Stripe bills: `max(1, seats)`. An agency with no creators yet still
|
|
379
|
+
* pays for one seat, and the screen must say the number Stripe charges.
|
|
380
|
+
*/
|
|
381
|
+
billedSeats: number;
|
|
382
|
+
price: {
|
|
383
|
+
amount: number;
|
|
384
|
+
currency: string;
|
|
385
|
+
interval: string;
|
|
386
|
+
} | null;
|
|
387
|
+
currentPeriodEnd?: string;
|
|
388
|
+
cancelAtPeriodEnd?: boolean;
|
|
389
|
+
nextInvoice?: {
|
|
390
|
+
amount: number;
|
|
391
|
+
currency: string;
|
|
392
|
+
date: string;
|
|
393
|
+
};
|
|
394
|
+
discount?: {
|
|
395
|
+
name?: string;
|
|
396
|
+
percentOff?: number;
|
|
397
|
+
amountOff?: number;
|
|
398
|
+
};
|
|
399
|
+
paymentMethod?: I_AgencyPaymentMethod;
|
|
400
|
+
billingDetails?: {
|
|
401
|
+
name?: string;
|
|
402
|
+
email?: string;
|
|
403
|
+
country?: string;
|
|
404
|
+
taxId?: string;
|
|
405
|
+
};
|
|
406
|
+
invoices: I_AgencyInvoice[];
|
|
407
|
+
covered: I_AgencyCoveredCreator[];
|
|
408
|
+
pastCreators: I_AgencyPastCreator[];
|
|
409
|
+
}
|
|
206
410
|
/**
|
|
207
411
|
* Every DTO below carries class-validator decorators and a constructor that
|
|
208
412
|
* survives being called with nothing.
|
|
@@ -264,10 +468,50 @@ export declare class AgencyDiscoverQueryDto {
|
|
|
264
468
|
constructor(data?: Partial<AgencyDiscoverQueryDto>);
|
|
265
469
|
}
|
|
266
470
|
export declare class AgencyAccessRequestDto {
|
|
471
|
+
/**
|
|
472
|
+
* A user id: a UUID or a Firebase uid (letters, digits, hyphens). It was
|
|
473
|
+
* `@IsUUID()` until US5 was built, which refused most real creators — their
|
|
474
|
+
* ids are Firebase uids.
|
|
475
|
+
*/
|
|
267
476
|
userId: string;
|
|
268
477
|
scopes: I_AgencyGrantScope[];
|
|
269
478
|
constructor(data?: Partial<AgencyAccessRequestDto>);
|
|
270
479
|
}
|
|
480
|
+
/** `PATCH /users/me/agency-discoverable` — "Let agencies find me" (FR-015). */
|
|
481
|
+
export declare class UpdateAgencyDiscoverableDto {
|
|
482
|
+
discoverable: boolean;
|
|
483
|
+
/**
|
|
484
|
+
* NO default: a required boolean that defaults to false lets an empty body
|
|
485
|
+
* through validation and reach the database as `undefined` (review M7).
|
|
486
|
+
*/
|
|
487
|
+
constructor(data?: Partial<UpdateAgencyDiscoverableDto>);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* One discover hit: the public handle and the platforms, NEVER a metric and
|
|
491
|
+
* never a grant flag, so the payload cannot tell a caller whether it already
|
|
492
|
+
* holds the creator (FR-015). `userId` is what the request is sent for.
|
|
493
|
+
*/
|
|
494
|
+
export interface I_AgencyDiscoverHit {
|
|
495
|
+
userId: string;
|
|
496
|
+
handle: string;
|
|
497
|
+
platforms: PublishPlatform[];
|
|
498
|
+
/**
|
|
499
|
+
* The platform whose username matched. Absent on an e-mail match. Two hits
|
|
500
|
+
* with the same handle are two different people (@mark on YouTube, another
|
|
501
|
+
* @mark on Instagram), and this is what tells them apart (T068).
|
|
502
|
+
*/
|
|
503
|
+
matchedPlatform?: PublishPlatform;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* `POST /agency/roster/discover` — the SAME shape and status for every case:
|
|
507
|
+
* discoverable exact matches, a non-discoverable account, a partial match and
|
|
508
|
+
* no account at all (SC-009). A miss is `{ results: [] }`, never a 404. An
|
|
509
|
+
* exact handle held by several findable creators returns each of them, one
|
|
510
|
+
* row per person (Maciej, 2026-09-26, T068); an e-mail matches one person.
|
|
511
|
+
*/
|
|
512
|
+
export interface I_AgencyDiscoverResult {
|
|
513
|
+
results: I_AgencyDiscoverHit[];
|
|
514
|
+
}
|
|
271
515
|
export declare class AdminSetOrganizationStatusDto {
|
|
272
516
|
status: Extract<AgencyOrganizationStatus, 'approved' | 'rejected'>;
|
|
273
517
|
/** Required on reject — a rejected applicant is shown this (FR-002). The
|
package/dist/types/agency.js
CHANGED
|
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.AdminSetOrganizationStatusDto = exports.AgencyAccessRequestDto = exports.AgencyDiscoverQueryDto = exports.SponsorCreatorDto = exports.UpdateGrantScopesDto = exports.AcceptGrantDto = exports.AcceptAgencyMemberInviteDto = exports.RedeemAgencyInviteDto = exports.InviteAgencyMemberDto = exports.CreateAgencyOrganizationDto = exports.AGENCY_ROSTER_NEEDS_ATTENTION_STATES = exports.AGENCY_ROSTER_ROW_STATES = exports.CREATOR_ACCESS_GRANT_STATES = exports.AGENCY_ORGANIZATION_STATUSES = exports.isAgencyGrantScope = exports.AGENCY_GRANT_SCOPES = exports.AGENCY_ROUTE_PATHS = void 0;
|
|
12
|
+
exports.AdminSetOrganizationStatusDto = exports.UpdateAgencyDiscoverableDto = exports.AgencyAccessRequestDto = exports.AgencyDiscoverQueryDto = exports.SponsorCreatorDto = exports.UpdateGrantScopesDto = exports.AcceptGrantDto = exports.AcceptAgencyMemberInviteDto = exports.RedeemAgencyInviteDto = exports.InviteAgencyMemberDto = exports.CreateAgencyOrganizationDto = exports.AGENCY_ROSTER_METRICS_STATES = exports.AGENCY_ROSTER_NEEDS_ATTENTION_STATES = exports.AGENCY_ROSTER_ROW_STATES = exports.AGENCY_REQUEST_CODES = exports.CREATOR_ACCESS_GRANT_STATES = exports.AGENCY_ORGANIZATION_STATUSES = exports.isAgencyGrantScope = exports.AGENCY_GRANT_SCOPES = exports.AGENCY_ROUTE_PATHS = void 0;
|
|
13
13
|
const class_validator_1 = require("class-validator");
|
|
14
14
|
// ── Cross-repo route constants ───────────────────────────────────────────────
|
|
15
15
|
/**
|
|
@@ -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
|
/**
|
|
@@ -89,6 +91,16 @@ exports.CREATOR_ACCESS_GRANT_STATES = [
|
|
|
89
91
|
'expired',
|
|
90
92
|
'revoked',
|
|
91
93
|
];
|
|
94
|
+
/**
|
|
95
|
+
* Outcomes of discovery and requests that are not refusals of access, sent as
|
|
96
|
+
* codes so the screen composes the sentence in the reader's language (FR-019,
|
|
97
|
+
* review M4). A 429 carries `{ code, limit, windowMinutes }`.
|
|
98
|
+
*/
|
|
99
|
+
exports.AGENCY_REQUEST_CODES = {
|
|
100
|
+
RATE_LIMITED: 'agency-rate-limited',
|
|
101
|
+
REQUEST_EXPIRED: 'agency-request-expired',
|
|
102
|
+
REQUEST_ANSWERED: 'agency-request-answered',
|
|
103
|
+
};
|
|
92
104
|
// ── Roster ───────────────────────────────────────────────────────────────────
|
|
93
105
|
/**
|
|
94
106
|
* Roster row states, DECLARED IN PRECEDENCE ORDER (data-model §Derived).
|
|
@@ -104,6 +116,13 @@ exports.AGENCY_ROSTER_ROW_STATES = [
|
|
|
104
116
|
'lapsed',
|
|
105
117
|
/** the creator has no live platform connection */
|
|
106
118
|
'disconnected',
|
|
119
|
+
/**
|
|
120
|
+
* shared within the last 24 hours and not synced yet: the first daily sync
|
|
121
|
+
* has not run. Not stale, and not something the agency can act on (T067,
|
|
122
|
+
* the 2026-09-26 UI run: a creator who agreed a minute ago read as
|
|
123
|
+
* "needs attention").
|
|
124
|
+
*/
|
|
125
|
+
'syncing',
|
|
107
126
|
/** no successful daily sync in 24 hours (FR-011, Jan 2026-09-21) */
|
|
108
127
|
'stale',
|
|
109
128
|
'active',
|
|
@@ -120,6 +139,22 @@ exports.AGENCY_ROSTER_NEEDS_ATTENTION_STATES = [
|
|
|
120
139
|
'disconnected',
|
|
121
140
|
'stale',
|
|
122
141
|
];
|
|
142
|
+
/**
|
|
143
|
+
* The rows that carry numbers — ONE home for "which creators' numbers may the
|
|
144
|
+
* agency see on the roster" (decided 2026-09-24, review M1/M2).
|
|
145
|
+
*
|
|
146
|
+
* Only a row whose card OPENS carries `metrics` and counts in the followers,
|
|
147
|
+
* followers-delta and comments totals: `active`, and `stale` (the card opens;
|
|
148
|
+
* the numbers are as old as `lastSyncAt` says). A `lapsed` creator's reads are
|
|
149
|
+
* refused on the card, so their numbers must not reach the agency through the
|
|
150
|
+
* roster instead; a `disconnected` creator has nothing live to count; a
|
|
151
|
+
* `requested` creator has shared nothing yet.
|
|
152
|
+
*/
|
|
153
|
+
exports.AGENCY_ROSTER_METRICS_STATES = [
|
|
154
|
+
'syncing',
|
|
155
|
+
'stale',
|
|
156
|
+
'active',
|
|
157
|
+
];
|
|
123
158
|
// ── DTOs ─────────────────────────────────────────────────────────────────────
|
|
124
159
|
/**
|
|
125
160
|
* Every DTO below carries class-validator decorators and a constructor that
|
|
@@ -272,7 +307,8 @@ class AgencyAccessRequestDto {
|
|
|
272
307
|
}
|
|
273
308
|
exports.AgencyAccessRequestDto = AgencyAccessRequestDto;
|
|
274
309
|
__decorate([
|
|
275
|
-
(0, class_validator_1.
|
|
310
|
+
(0, class_validator_1.IsString)(),
|
|
311
|
+
(0, class_validator_1.Matches)(/^[A-Za-z0-9-]{1,128}$/),
|
|
276
312
|
__metadata("design:type", String)
|
|
277
313
|
], AgencyAccessRequestDto.prototype, "userId", void 0);
|
|
278
314
|
__decorate([
|
|
@@ -281,6 +317,22 @@ __decorate([
|
|
|
281
317
|
(0, class_validator_1.IsIn)(exports.AGENCY_GRANT_SCOPES, { each: true }),
|
|
282
318
|
__metadata("design:type", Array)
|
|
283
319
|
], AgencyAccessRequestDto.prototype, "scopes", void 0);
|
|
320
|
+
/** `PATCH /users/me/agency-discoverable` — "Let agencies find me" (FR-015). */
|
|
321
|
+
class UpdateAgencyDiscoverableDto {
|
|
322
|
+
/**
|
|
323
|
+
* NO default: a required boolean that defaults to false lets an empty body
|
|
324
|
+
* through validation and reach the database as `undefined` (review M7).
|
|
325
|
+
*/
|
|
326
|
+
constructor(data) {
|
|
327
|
+
if (data?.discoverable !== undefined)
|
|
328
|
+
this.discoverable = data.discoverable;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
exports.UpdateAgencyDiscoverableDto = UpdateAgencyDiscoverableDto;
|
|
332
|
+
__decorate([
|
|
333
|
+
(0, class_validator_1.IsBoolean)(),
|
|
334
|
+
__metadata("design:type", Boolean)
|
|
335
|
+
], UpdateAgencyDiscoverableDto.prototype, "discoverable", void 0);
|
|
284
336
|
class AdminSetOrganizationStatusDto {
|
|
285
337
|
constructor(data) {
|
|
286
338
|
this.status = data?.status ?? 'approved';
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -57,5 +57,7 @@ __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-threads"), exports);
|
|
60
62
|
// ── Access control ────────────────────────────────────────────────────────────
|
|
61
63
|
__exportStar(require("./I_AccessDenial"), exports);
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Covers subscription tier state, Stripe checkout flows,
|
|
5
5
|
* and the customer portal redirect.
|
|
6
6
|
*/
|
|
7
|
+
import type { I_SponsoredBy, I_SponsorshipEnded } from '../agency';
|
|
7
8
|
export type SchedulingSubscriptionTier = 'free' | 'starter' | 'pro';
|
|
8
9
|
export type SchedulingSubscriptionStatus = 'active' | 'canceled' | 'expired';
|
|
9
10
|
/**
|
|
10
11
|
* Constructor input. Takes `maxAccounts`, or the deprecated `maxPlatforms` so
|
|
11
12
|
* code written against 1.40.0 keeps compiling; either way both fields are set.
|
|
12
13
|
*/
|
|
13
|
-
export type SchedulingSubscriptionInfoInput = Pick<SchedulingSubscriptionInfoDTO, 'tier' | 'status' | 'expiresAt'> & ({
|
|
14
|
+
export type SchedulingSubscriptionInfoInput = Pick<SchedulingSubscriptionInfoDTO, 'tier' | 'status' | 'expiresAt'> & Partial<Pick<SchedulingSubscriptionInfoDTO, 'sponsoredBy' | 'sponsorshipEnded'>> & ({
|
|
14
15
|
maxAccounts: number;
|
|
15
16
|
maxPlatforms?: number;
|
|
16
17
|
} | {
|
|
@@ -32,6 +33,14 @@ export declare class SchedulingSubscriptionInfoDTO {
|
|
|
32
33
|
* Read `maxAccounts`. Always equal to it; removed in a later release.
|
|
33
34
|
*/
|
|
34
35
|
maxPlatforms: number;
|
|
36
|
+
/**
|
|
37
|
+
* Present only while an agency covers this creator's plan (spec 175 FR-014).
|
|
38
|
+
* `until` appears once the sponsorship is ending: the date the plan runs to.
|
|
39
|
+
* Absent — never null — for everyone else, so "not sponsored" has one shape.
|
|
40
|
+
*/
|
|
41
|
+
sponsoredBy?: I_SponsoredBy;
|
|
42
|
+
/** Present only when a coverage ended recently and nothing replaced it. Absent otherwise. */
|
|
43
|
+
sponsorshipEnded?: I_SponsorshipEnded;
|
|
35
44
|
constructor(data: SchedulingSubscriptionInfoInput);
|
|
36
45
|
}
|
|
37
46
|
/**
|
|
@@ -19,6 +19,10 @@ class SchedulingSubscriptionInfoDTO {
|
|
|
19
19
|
this.expiresAt = data.expiresAt;
|
|
20
20
|
this.maxAccounts = maxAccounts;
|
|
21
21
|
this.maxPlatforms = maxAccounts;
|
|
22
|
+
if (data.sponsoredBy)
|
|
23
|
+
this.sponsoredBy = data.sponsoredBy;
|
|
24
|
+
if (data.sponsorshipEnded)
|
|
25
|
+
this.sponsorshipEnded = data.sponsorshipEnded;
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
exports.SchedulingSubscriptionInfoDTO = SchedulingSubscriptionInfoDTO;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ad2app-lib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.49.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"prepare": "npm run build"
|
|
52
52
|
},
|
|
53
53
|
"keywords": [],
|
|
54
|
-
"author": "Maciej
|
|
54
|
+
"author": "Maciej Górski@ad2.app",
|
|
55
55
|
"license": "ISC",
|
|
56
56
|
"description": "Package to share types and utils across the ad2app projects",
|
|
57
57
|
"dependencies": {
|
|
@@ -196,11 +196,51 @@ 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" },
|
|
199
239
|
};
|
|
200
240
|
|
|
201
241
|
test("no agency event may carry the discover query — only whether it matched (FR-015)", () => {
|
|
202
242
|
const agencyEvents = Object.values(EVENTS).filter((name) => name.startsWith("agency_"));
|
|
203
|
-
assert.equal(agencyEvents.length,
|
|
243
|
+
assert.equal(agencyEvents.length, 29, "the 175 set is 15 roster events, 12 campaign-loop events (US7) and 2 thread events (US8)");
|
|
204
244
|
for (const name of agencyEvents) {
|
|
205
245
|
const props = EVENT_PROPERTY_WITNESS[name as keyof typeof EVENT_PROPERTY_WITNESS];
|
|
206
246
|
for (const key of Object.keys(props)) {
|
package/src/analytics/index.ts
CHANGED
|
@@ -191,6 +191,23 @@ export const EVENTS = {
|
|
|
191
191
|
// creators an agency went looking for.
|
|
192
192
|
AGENCY_DISCOVER_SEARCHED: 'agency_discover_searched',
|
|
193
193
|
AGENCY_ACCESS_REQUESTED: 'agency_access_requested',
|
|
194
|
+
// The campaign loop (175 US7). Server-owned: each is captured in the act's
|
|
195
|
+
// own transaction path, under the ACTOR's distinct id.
|
|
196
|
+
AGENCY_CAMPAIGN_CREATED: 'agency_campaign_created',
|
|
197
|
+
AGENCY_CAMPAIGN_CREATOR_INVITED: 'agency_campaign_creator_invited',
|
|
198
|
+
AGENCY_CAMPAIGN_JOINED: 'agency_campaign_joined',
|
|
199
|
+
AGENCY_CAMPAIGN_DECLINED: 'agency_campaign_declined',
|
|
200
|
+
AGENCY_CAMPAIGN_LEFT: 'agency_campaign_left',
|
|
201
|
+
AGENCY_CAMPAIGN_CREATOR_REMOVED: 'agency_campaign_creator_removed',
|
|
202
|
+
AGENCY_CAMPAIGN_OFFER_SENT: 'agency_campaign_offer_sent',
|
|
203
|
+
AGENCY_CAMPAIGN_OFFER_ACTED: 'agency_campaign_offer_acted',
|
|
204
|
+
AGENCY_CAMPAIGN_MATERIAL_UPLOADED: 'agency_campaign_material_uploaded',
|
|
205
|
+
AGENCY_CAMPAIGN_MATERIAL_REVIEWED: 'agency_campaign_material_reviewed',
|
|
206
|
+
AGENCY_CAMPAIGN_PUBLISHED: 'agency_campaign_published',
|
|
207
|
+
AGENCY_CAMPAIGN_MILESTONE_CONFIRMED: 'agency_campaign_milestone_confirmed',
|
|
208
|
+
// Threads (175 US8). Never the message body: a conversation is not analytics.
|
|
209
|
+
AGENCY_THREAD_STARTED: 'agency_thread_started',
|
|
210
|
+
AGENCY_THREAD_MESSAGE_SENT: 'agency_thread_message_sent',
|
|
194
211
|
} as const;
|
|
195
212
|
|
|
196
213
|
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
@@ -427,6 +444,11 @@ export interface AgencyEventProperties {
|
|
|
427
444
|
creator_user_id?: string;
|
|
428
445
|
}
|
|
429
446
|
|
|
447
|
+
/** A campaign-loop event: the agency event plus the campaign it concerns. */
|
|
448
|
+
export interface AgencyCampaignEventProperties extends AgencyEventProperties {
|
|
449
|
+
campaign_id: string;
|
|
450
|
+
}
|
|
451
|
+
|
|
430
452
|
export interface EventProperties {
|
|
431
453
|
[EVENTS.LANDING_CTA_CLICKED]: {
|
|
432
454
|
location:
|
|
@@ -675,6 +697,23 @@ export interface EventProperties {
|
|
|
675
697
|
// `matched` only. The query itself is never sent (FR-015).
|
|
676
698
|
[EVENTS.AGENCY_DISCOVER_SEARCHED]: AgencyEventProperties & { matched: boolean };
|
|
677
699
|
[EVENTS.AGENCY_ACCESS_REQUESTED]: AgencyEventProperties;
|
|
700
|
+
// The campaign loop (175 US7): which campaign, and for an act, which one.
|
|
701
|
+
// Never a price: an amount in analytics is a creator's rate in a dashboard.
|
|
702
|
+
[EVENTS.AGENCY_CAMPAIGN_CREATED]: AgencyCampaignEventProperties;
|
|
703
|
+
[EVENTS.AGENCY_CAMPAIGN_CREATOR_INVITED]: AgencyCampaignEventProperties & { with_access_request: boolean };
|
|
704
|
+
[EVENTS.AGENCY_CAMPAIGN_JOINED]: AgencyCampaignEventProperties;
|
|
705
|
+
[EVENTS.AGENCY_CAMPAIGN_DECLINED]: AgencyCampaignEventProperties;
|
|
706
|
+
[EVENTS.AGENCY_CAMPAIGN_LEFT]: AgencyCampaignEventProperties;
|
|
707
|
+
[EVENTS.AGENCY_CAMPAIGN_CREATOR_REMOVED]: AgencyCampaignEventProperties;
|
|
708
|
+
[EVENTS.AGENCY_CAMPAIGN_OFFER_SENT]: AgencyCampaignEventProperties;
|
|
709
|
+
[EVENTS.AGENCY_CAMPAIGN_OFFER_ACTED]: AgencyCampaignEventProperties & { action: 'accept' | 'counter' | 'confirm' | 'reject' };
|
|
710
|
+
[EVENTS.AGENCY_CAMPAIGN_MATERIAL_UPLOADED]: AgencyCampaignEventProperties & { version: number };
|
|
711
|
+
[EVENTS.AGENCY_CAMPAIGN_MATERIAL_REVIEWED]: AgencyCampaignEventProperties & { action: 'approve' | 'request_changes' | 'reject' };
|
|
712
|
+
[EVENTS.AGENCY_CAMPAIGN_PUBLISHED]: AgencyCampaignEventProperties;
|
|
713
|
+
[EVENTS.AGENCY_CAMPAIGN_MILESTONE_CONFIRMED]: AgencyCampaignEventProperties;
|
|
714
|
+
// Threads (175 US8): which thread, never its content.
|
|
715
|
+
[EVENTS.AGENCY_THREAD_STARTED]: AgencyEventProperties & { thread_id: string };
|
|
716
|
+
[EVENTS.AGENCY_THREAD_MESSAGE_SENT]: AgencyEventProperties & { thread_id: string };
|
|
678
717
|
}
|
|
679
718
|
|
|
680
719
|
/** Canonical person property keys (set via identify / $set). */
|
|
@@ -82,6 +82,25 @@ export const ACCESS_DENIAL_CODES = {
|
|
|
82
82
|
* per person, FR-004). Names the case and NOTHING else about that account.
|
|
83
83
|
*/
|
|
84
84
|
AGENCY_ALREADY_IN_ORGANIZATION: "agency-already-in-organization",
|
|
85
|
+
/**
|
|
86
|
+
* The owner invited an address that is ALREADY on this organization's team,
|
|
87
|
+
* themselves included (T063). A different case from the one above: the owner
|
|
88
|
+
* is told this person is already in, not that they belong elsewhere.
|
|
89
|
+
*/
|
|
90
|
+
AGENCY_ALREADY_MEMBER: "agency-already-member",
|
|
91
|
+
/**
|
|
92
|
+
* The owner tried to cover a creator who pays for their own plan. Covering
|
|
93
|
+
* is for a creator without a paid plan (US4 scenario 3); covering a payer
|
|
94
|
+
* would leave them paying for what the agency also pays for (T066).
|
|
95
|
+
*/
|
|
96
|
+
AGENCY_CREATOR_PAYS_OWN_PLAN: "agency-creator-pays-own-plan",
|
|
97
|
+
/**
|
|
98
|
+
* A creator whose plan an agency covers asked for the billing portal, and
|
|
99
|
+
* has no Stripe customer of their own to open it for (FR-014). The body
|
|
100
|
+
* names the covering agency; the client hides the portal button instead of
|
|
101
|
+
* showing a Stripe error.
|
|
102
|
+
*/
|
|
103
|
+
SPONSORED_NO_PORTAL: "sponsored-no-portal",
|
|
85
104
|
} as const;
|
|
86
105
|
|
|
87
106
|
export type AccessDenialCode =
|
package/src/types/I_Offer.ts
CHANGED
|
@@ -41,6 +41,8 @@ export enum I_OfferStatus {
|
|
|
41
41
|
AGENCY_NEGOTIATED = 'agencyNegotiated',
|
|
42
42
|
CONFIRMED = 'confirmed',
|
|
43
43
|
ALTERNATIVE_SUGGESTED = 'alternativeSuggested',
|
|
44
|
+
/** The creator left the campaign before the offer was confirmed (spec 175 US7). Added, never renamed: Postgres enums only grow. */
|
|
45
|
+
WITHDRAWN = 'withdrawn',
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
export declare class I_OfferGetDTO {
|
|
@@ -87,7 +87,7 @@ test('every AGENCY code is namespaced, so a new domain cannot shadow an old wall
|
|
|
87
87
|
key.startsWith('AGENCY_'),
|
|
88
88
|
);
|
|
89
89
|
|
|
90
|
-
assert.equal(agencyCodes.length,
|
|
90
|
+
assert.equal(agencyCodes.length, 14);
|
|
91
91
|
for (const [, value] of agencyCodes) {
|
|
92
92
|
assert.match(value, /^agency-/, `un-namespaced agency code: ${value}`);
|
|
93
93
|
}
|