ad2app-lib 1.47.0 → 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_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 +2 -0
- package/dist/types/agency.js +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/package.json +1 -1
- package/src/analytics/index.test.ts +41 -1
- package/src/analytics/index.ts +39 -0
- package/src/types/I_Offer.ts +2 -0
- package/src/types/agency-campaigns.test.ts +85 -0
- package/src/types/agency-campaigns.ts +663 -0
- package/src/types/agency-threads.ts +103 -0
- package/src/types/agency.ts +2 -0
- package/src/types/index.ts +2 -0
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). */
|
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 {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AGENCY_CAMPAIGN_STEPS,
|
|
6
|
+
AGENCY_OFFER_STATUSES,
|
|
7
|
+
CAMPAIGN_PLACEMENTS,
|
|
8
|
+
CampaignOfferActDto,
|
|
9
|
+
CreateAgencyCampaignDto,
|
|
10
|
+
SETTLEMENT_FORMS,
|
|
11
|
+
isCampaignPlacement,
|
|
12
|
+
} from "./agency-campaigns";
|
|
13
|
+
import { I_OfferStatus } from "./I_Offer";
|
|
14
|
+
|
|
15
|
+
test("the placements list is exactly FR-025's, one list per platform", () => {
|
|
16
|
+
assert.deepEqual(CAMPAIGN_PLACEMENTS, {
|
|
17
|
+
instagram: ["reel", "post", "carousel", "stories", "collab_post"],
|
|
18
|
+
tiktok: ["video", "repost", "duet", "stories"],
|
|
19
|
+
youtube: ["video", "short", "integration", "community_post"],
|
|
20
|
+
facebook: ["post", "reel", "stories"],
|
|
21
|
+
linkedin: ["post", "article"],
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("isCampaignPlacement checks the PAIR, and fails closed", () => {
|
|
26
|
+
assert.equal(isCampaignPlacement("instagram", "reel"), true);
|
|
27
|
+
assert.equal(isCampaignPlacement("youtube", "short"), true);
|
|
28
|
+
// A real placement on the wrong platform is not a placement.
|
|
29
|
+
assert.equal(isCampaignPlacement("linkedin", "reel"), false);
|
|
30
|
+
assert.equal(isCampaignPlacement("twitter", "post"), false);
|
|
31
|
+
// Prototype keys are not platforms.
|
|
32
|
+
assert.equal(isCampaignPlacement("constructor", "post"), false);
|
|
33
|
+
assert.equal(isCampaignPlacement("__proto__", "post"), false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("withdrawn is ADDED to the offer enum and the agency loop never writes the legacy-only values", () => {
|
|
37
|
+
assert.equal(I_OfferStatus.WITHDRAWN, "withdrawn");
|
|
38
|
+
for (const legacyOnly of [I_OfferStatus.DRAFT, I_OfferStatus.DELETED, I_OfferStatus.ALTERNATIVE_SUGGESTED]) {
|
|
39
|
+
assert.equal((AGENCY_OFFER_STATUSES as readonly string[]).includes(legacyOnly), false);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("the board has FR-024's columns in loop order", () => {
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
[...AGENCY_CAMPAIGN_STEPS],
|
|
46
|
+
["invited", "joined", "offer_sent", "negotiating", "confirmed", "material_in", "approved", "published"],
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("the settlement forms are the creator's three (FR-027)", () => {
|
|
51
|
+
assert.deepEqual([...SETTLEMENT_FORMS], ["invoice", "contract", "barter"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("DTOs construct with no arguments, as the validation pipe does", () => {
|
|
55
|
+
assert.doesNotThrow(() => new CreateAgencyCampaignDto());
|
|
56
|
+
assert.doesNotThrow(() => new CampaignOfferActDto());
|
|
57
|
+
assert.equal(new CreateAgencyCampaignDto({ name: "Spring", text: "Concept" }).text, "Concept");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a material is a raster image or a video: SVG and HTML are refused (review L5)", async () => {
|
|
61
|
+
const { validate } = await import("class-validator");
|
|
62
|
+
const { PrepareMaterialUploadDto } = await import("./agency-campaigns");
|
|
63
|
+
const check = async (contentType: string) =>
|
|
64
|
+
(await validate(new PrepareMaterialUploadDto({ fileName: "f", contentType, sizeBytes: 1 }))).length === 0;
|
|
65
|
+
assert.equal(await check("video/mp4"), true);
|
|
66
|
+
assert.equal(await check("image/jpeg"), true);
|
|
67
|
+
assert.equal(await check("image/svg+xml"), false);
|
|
68
|
+
assert.equal(await check("text/html"), false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("thread messages are bounded and never empty (US8)", async () => {
|
|
72
|
+
const { validate } = await import("class-validator");
|
|
73
|
+
const { PostThreadMessageDto, THREAD_MESSAGE_MAX, AGENCY_THREAD_CODES } = await import("./agency-threads");
|
|
74
|
+
assert.equal((await validate(new PostThreadMessageDto({ text: "hi" }))).length, 0);
|
|
75
|
+
assert.notEqual((await validate(new PostThreadMessageDto({ text: "" }))).length, 0);
|
|
76
|
+
assert.notEqual((await validate(new PostThreadMessageDto({ text: "x".repeat(THREAD_MESSAGE_MAX + 1) }))).length, 0);
|
|
77
|
+
assert.equal(AGENCY_THREAD_CODES.CLOSED, "agency-thread-closed");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("the picker's photo list and the API's rule are one rule (epic verify E7)", async () => {
|
|
81
|
+
const { MATERIAL_CONTENT_TYPE, MATERIAL_IMAGE_TYPES } = await import("./agency-campaigns");
|
|
82
|
+
for (const type of MATERIAL_IMAGE_TYPES) assert.equal(MATERIAL_CONTENT_TYPE.test(type), true, type);
|
|
83
|
+
for (const type of ["image/svg+xml", "text/plain", "image/pngx", "application/octet-stream", ""]) assert.equal(MATERIAL_CONTENT_TYPE.test(type), false, type);
|
|
84
|
+
assert.equal(MATERIAL_CONTENT_TYPE.test("video/quicktime"), true);
|
|
85
|
+
});
|