ad2app-lib 1.17.0 → 1.20.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 +206 -0
- package/dist/analytics/index.js +41 -0
- package/dist/types/scheduling/I_SchedulingInbox.d.ts +165 -0
- package/dist/types/scheduling/I_SchedulingInbox.js +186 -1
- package/dist/types/scheduling/I_SchedulingPost.d.ts +7 -1
- package/dist/types/scheduling/I_SchedulingPost.js +1 -0
- package/package.json +2 -2
- package/src/analytics/index.test.ts +199 -2
- package/src/analytics/index.ts +187 -1
- package/src/types/scheduling/I_SchedulingInbox.test.ts +218 -1
- package/src/types/scheduling/I_SchedulingInbox.ts +289 -0
- package/src/types/scheduling/I_SchedulingPost.test.ts +24 -0
- package/src/types/scheduling/I_SchedulingPost.ts +13 -1
|
@@ -82,6 +82,12 @@ export class SchedulingInboxCommentDTO {
|
|
|
82
82
|
/** Parent comment id on nested replies (AD2-1088). Absent on top-level comments. */
|
|
83
83
|
parentId?: string;
|
|
84
84
|
replies?: SchedulingInboxCommentDTO[];
|
|
85
|
+
/**
|
|
86
|
+
* True when this comment can be moderated (approve/reject/hold) — YouTube only
|
|
87
|
+
* per Zernio (OpenAPI v1.0.4:22489, `platform` enum = [youtube]). Absent for
|
|
88
|
+
* every other platform; never fabricated to false-as-if-known (082 US7).
|
|
89
|
+
*/
|
|
90
|
+
canModerate?: boolean;
|
|
85
91
|
|
|
86
92
|
constructor(data: SchedulingInboxCommentDTO) {
|
|
87
93
|
this.id = data.id;
|
|
@@ -94,6 +100,7 @@ export class SchedulingInboxCommentDTO {
|
|
|
94
100
|
this.isOwner = data.isOwner;
|
|
95
101
|
this.parentId = data.parentId;
|
|
96
102
|
this.replies = data.replies;
|
|
103
|
+
this.canModerate = data.canModerate;
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
106
|
|
|
@@ -128,3 +135,285 @@ export class SchedulingReplyToCommentDTO {
|
|
|
128
135
|
this.commentId = data.commentId;
|
|
129
136
|
}
|
|
130
137
|
}
|
|
138
|
+
|
|
139
|
+
// ── 082 Inbox Parity — new inbox surfaces (US1) ───────────────────────────────
|
|
140
|
+
//
|
|
141
|
+
// Shapes below mirror the Zernio OpenAPI v1.0.4 inbox surface (line anchors in
|
|
142
|
+
// each comment); ad2app must NOT invent fields (AD2-1038). Availability is
|
|
143
|
+
// honest-or-absent: an optional field is absent when Zernio does not report it,
|
|
144
|
+
// never zero/false-backfilled.
|
|
145
|
+
|
|
146
|
+
// ── SchedulingInboxMessageDTO / SchedulingInboxMessagePageDTO ──────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* One message inside a DM thread.
|
|
150
|
+
* Returned (paginated) by GET /social/inbox/messages/:id/thread — backed by
|
|
151
|
+
* Zernio GET /v1/inbox/conversations/{id}/messages (OpenAPI v1.0.4:20845).
|
|
152
|
+
*/
|
|
153
|
+
export class SchedulingInboxMessageDTO {
|
|
154
|
+
id: string;
|
|
155
|
+
conversationId: string;
|
|
156
|
+
authorId: string;
|
|
157
|
+
authorName: string;
|
|
158
|
+
text: string;
|
|
159
|
+
createdAt: string;
|
|
160
|
+
/** True when OUR connected account authored this message. */
|
|
161
|
+
isOwn: boolean;
|
|
162
|
+
mediaUrl?: string;
|
|
163
|
+
|
|
164
|
+
constructor(data: SchedulingInboxMessageDTO) {
|
|
165
|
+
this.id = data.id;
|
|
166
|
+
this.conversationId = data.conversationId;
|
|
167
|
+
this.authorId = data.authorId;
|
|
168
|
+
this.authorName = data.authorName;
|
|
169
|
+
this.text = data.text;
|
|
170
|
+
this.createdAt = data.createdAt;
|
|
171
|
+
this.isOwn = data.isOwn;
|
|
172
|
+
this.mediaUrl = data.mediaUrl;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* A cursor page of thread messages — kills the prior 100-message truncation.
|
|
178
|
+
* `nextCursor` absent = last page.
|
|
179
|
+
*/
|
|
180
|
+
export class SchedulingInboxMessagePageDTO {
|
|
181
|
+
items: SchedulingInboxMessageDTO[];
|
|
182
|
+
nextCursor?: string;
|
|
183
|
+
|
|
184
|
+
constructor(data: SchedulingInboxMessagePageDTO) {
|
|
185
|
+
this.items = data.items;
|
|
186
|
+
this.nextCursor = data.nextCursor;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── SchedulingConversationReadDTO ─────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Mark-read acknowledgement — POST /social/inbox/messages/:id/read → Zernio
|
|
194
|
+
* POST /v1/inbox/conversations/{id}/read (OpenAPI v1.0.4:21692). `unreadCount`
|
|
195
|
+
* absent unless Zernio reports the post-read count.
|
|
196
|
+
*/
|
|
197
|
+
export class SchedulingConversationReadDTO {
|
|
198
|
+
conversationId: string;
|
|
199
|
+
unreadCount?: number;
|
|
200
|
+
|
|
201
|
+
constructor(data: SchedulingConversationReadDTO) {
|
|
202
|
+
this.conversationId = data.conversationId;
|
|
203
|
+
this.unreadCount = data.unreadCount;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── SchedulingInboxMentionDTO / SchedulingReplyToMentionDTO ────────────────────
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* An inbound mention.
|
|
211
|
+
* Returned by GET /social/inbox/mentions → Zernio GET /v1/inbox/mentions
|
|
212
|
+
* (OpenAPI v1.0.4:23108). Source post fields absent when the platform omits them.
|
|
213
|
+
*/
|
|
214
|
+
export class SchedulingInboxMentionDTO {
|
|
215
|
+
id: string;
|
|
216
|
+
platform: string;
|
|
217
|
+
accountId: string;
|
|
218
|
+
authorName: string;
|
|
219
|
+
authorAvatarUrl?: string;
|
|
220
|
+
text: string;
|
|
221
|
+
createdAt: string;
|
|
222
|
+
sourcePostId?: string;
|
|
223
|
+
sourcePostUrl?: string;
|
|
224
|
+
|
|
225
|
+
constructor(data: SchedulingInboxMentionDTO) {
|
|
226
|
+
this.id = data.id;
|
|
227
|
+
this.platform = data.platform;
|
|
228
|
+
this.accountId = data.accountId;
|
|
229
|
+
this.authorName = data.authorName;
|
|
230
|
+
this.authorAvatarUrl = data.authorAvatarUrl;
|
|
231
|
+
this.text = data.text;
|
|
232
|
+
this.createdAt = data.createdAt;
|
|
233
|
+
this.sourcePostId = data.sourcePostId;
|
|
234
|
+
this.sourcePostUrl = data.sourcePostUrl;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Input for replying to a mention. Zernio requires [accountId, mediaId, message]
|
|
240
|
+
* (OpenAPI v1.0.4:23207) — `mediaId` (the mention's target media) is mandatory,
|
|
241
|
+
* unlike a DM reply.
|
|
242
|
+
*/
|
|
243
|
+
export class SchedulingReplyToMentionDTO {
|
|
244
|
+
accountId: string;
|
|
245
|
+
mediaId: string;
|
|
246
|
+
message: string;
|
|
247
|
+
|
|
248
|
+
constructor(data: SchedulingReplyToMentionDTO) {
|
|
249
|
+
this.accountId = data.accountId;
|
|
250
|
+
this.mediaId = data.mediaId;
|
|
251
|
+
this.message = data.message;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── SchedulingInboxReviewDTO / SchedulingReplyToReviewDTO ──────────────────────
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* A platform review (e.g. Google Business).
|
|
259
|
+
* Returned by GET /social/inbox/reviews → Zernio GET /v1/inbox/reviews
|
|
260
|
+
* (OpenAPI v1.0.4:23269). `replied` absent unless Zernio reports reply state.
|
|
261
|
+
*/
|
|
262
|
+
export class SchedulingInboxReviewDTO {
|
|
263
|
+
id: string;
|
|
264
|
+
platform: string;
|
|
265
|
+
accountId: string;
|
|
266
|
+
authorName: string;
|
|
267
|
+
rating: number;
|
|
268
|
+
text: string;
|
|
269
|
+
createdAt: string;
|
|
270
|
+
replied?: boolean;
|
|
271
|
+
|
|
272
|
+
constructor(data: SchedulingInboxReviewDTO) {
|
|
273
|
+
this.id = data.id;
|
|
274
|
+
this.platform = data.platform;
|
|
275
|
+
this.accountId = data.accountId;
|
|
276
|
+
this.authorName = data.authorName;
|
|
277
|
+
this.rating = data.rating;
|
|
278
|
+
this.text = data.text;
|
|
279
|
+
this.createdAt = data.createdAt;
|
|
280
|
+
this.replied = data.replied;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Input for replying to a review — POST /social/inbox/reviews/:reviewId/reply →
|
|
286
|
+
* Zernio POST /v1/inbox/reviews/{reviewId}/reply (OpenAPI v1.0.4:23377).
|
|
287
|
+
*/
|
|
288
|
+
export class SchedulingReplyToReviewDTO {
|
|
289
|
+
reviewId: string;
|
|
290
|
+
accountId: string;
|
|
291
|
+
text: string;
|
|
292
|
+
|
|
293
|
+
constructor(data: SchedulingReplyToReviewDTO) {
|
|
294
|
+
this.reviewId = data.reviewId;
|
|
295
|
+
this.accountId = data.accountId;
|
|
296
|
+
this.text = data.text;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ── SchedulingCommentModerationDTO ────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The moderation decision on a comment. Closed set per Zernio
|
|
304
|
+
* (OpenAPI v1.0.4:22489): `published` approves, `rejected` removes,
|
|
305
|
+
* `heldForReview` returns it to the queue. YouTube only. `banAuthor` is valid
|
|
306
|
+
* ONLY with `rejected` (any other pairing is a 400 — enforced server-side).
|
|
307
|
+
*/
|
|
308
|
+
export type SchedulingCommentModerationStatus = 'published' | 'rejected' | 'heldForReview';
|
|
309
|
+
|
|
310
|
+
export class SchedulingCommentModerationDTO {
|
|
311
|
+
moderationStatus: SchedulingCommentModerationStatus;
|
|
312
|
+
banAuthor?: boolean;
|
|
313
|
+
|
|
314
|
+
constructor(data: SchedulingCommentModerationDTO) {
|
|
315
|
+
this.moderationStatus = data.moderationStatus;
|
|
316
|
+
this.banAuthor = data.banAuthor;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ── SchedulingCreateConversationDTO ───────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Input for starting a new DM — POST /social/inbox/conversations → Zernio
|
|
324
|
+
* POST /v1/inbox/conversations (OpenAPI v1.0.4:20355). WhatsApp is excluded
|
|
325
|
+
* (it requires an approved template to open a thread; 082 non-goal).
|
|
326
|
+
*/
|
|
327
|
+
export class SchedulingCreateConversationDTO {
|
|
328
|
+
accountId: string;
|
|
329
|
+
recipientId: string;
|
|
330
|
+
text: string;
|
|
331
|
+
|
|
332
|
+
constructor(data: SchedulingCreateConversationDTO) {
|
|
333
|
+
this.accountId = data.accountId;
|
|
334
|
+
this.recipientId = data.recipientId;
|
|
335
|
+
this.text = data.text;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── SchedulingInboxCapabilityDTO ──────────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Per-connected-account inbox capability truth — which surfaces a platform
|
|
343
|
+
* actually supports. Drives honest showing/hiding (082 FR-4): a false/absent
|
|
344
|
+
* surface is HIDDEN, never rendered as a fabricated empty. Reconciled from
|
|
345
|
+
* platform-capabilities + Zernio /accounts/health.
|
|
346
|
+
*/
|
|
347
|
+
export class SchedulingInboxCapabilityDTO {
|
|
348
|
+
platform: string;
|
|
349
|
+
dm: boolean;
|
|
350
|
+
comments: boolean;
|
|
351
|
+
mentions: boolean;
|
|
352
|
+
reviews: boolean;
|
|
353
|
+
/** Can INITIATE a DM (not just reply). */
|
|
354
|
+
dmInit: boolean;
|
|
355
|
+
/** Comment moderation queue (YouTube). */
|
|
356
|
+
moderation: boolean;
|
|
357
|
+
|
|
358
|
+
constructor(data: SchedulingInboxCapabilityDTO) {
|
|
359
|
+
this.platform = data.platform;
|
|
360
|
+
this.dm = data.dm;
|
|
361
|
+
this.comments = data.comments;
|
|
362
|
+
this.mentions = data.mentions;
|
|
363
|
+
this.reviews = data.reviews;
|
|
364
|
+
this.dmInit = data.dmInit;
|
|
365
|
+
this.moderation = data.moderation;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ── SchedulingCrossPostCommentDTO ─────────────────────────────────────────────
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* A comment re-sourced from Zernio's cross-post inbox (GET /v1/inbox/comments),
|
|
373
|
+
* carrying its OWN post attribution so it surfaces even when the post is absent
|
|
374
|
+
* from our local SocialPostCache. The cache is enrichment ONLY (post snippet /
|
|
375
|
+
* thumbnail), never the source of truth (082 US3 / AD2-1110): a comment on an
|
|
376
|
+
* uncached post must still reach the moderator. Comment/engagement fields mirror
|
|
377
|
+
* SchedulingInboxCommentDTO; enrichment fields are honest-or-absent — filled from
|
|
378
|
+
* the cache when present, never zero/empty-backfilled.
|
|
379
|
+
*/
|
|
380
|
+
export class SchedulingCrossPostCommentDTO {
|
|
381
|
+
// Comment identity + content (mirrors SchedulingInboxCommentDTO).
|
|
382
|
+
id: string;
|
|
383
|
+
authorName: string;
|
|
384
|
+
authorAvatarUrl?: string;
|
|
385
|
+
text: string;
|
|
386
|
+
createdAt: string;
|
|
387
|
+
likeCount?: number;
|
|
388
|
+
replyCount?: number;
|
|
389
|
+
isOwner?: boolean;
|
|
390
|
+
parentId?: string;
|
|
391
|
+
canModerate?: boolean;
|
|
392
|
+
replies?: SchedulingCrossPostCommentDTO[];
|
|
393
|
+
// Post attribution — from the cross-post payload, NOT the cache (the whole point of US3).
|
|
394
|
+
postId: string;
|
|
395
|
+
platform: string;
|
|
396
|
+
accountId: string;
|
|
397
|
+
// Enrichment — from SocialPostCache WHEN present, absent otherwise (never backfilled).
|
|
398
|
+
postContentSnippet?: string;
|
|
399
|
+
postThumbnailUrl?: string;
|
|
400
|
+
|
|
401
|
+
constructor(data: SchedulingCrossPostCommentDTO) {
|
|
402
|
+
this.id = data.id;
|
|
403
|
+
this.authorName = data.authorName;
|
|
404
|
+
this.authorAvatarUrl = data.authorAvatarUrl;
|
|
405
|
+
this.text = data.text;
|
|
406
|
+
this.createdAt = data.createdAt;
|
|
407
|
+
this.likeCount = data.likeCount;
|
|
408
|
+
this.replyCount = data.replyCount;
|
|
409
|
+
this.isOwner = data.isOwner;
|
|
410
|
+
this.parentId = data.parentId;
|
|
411
|
+
this.canModerate = data.canModerate;
|
|
412
|
+
this.replies = data.replies;
|
|
413
|
+
this.postId = data.postId;
|
|
414
|
+
this.platform = data.platform;
|
|
415
|
+
this.accountId = data.accountId;
|
|
416
|
+
this.postContentSnippet = data.postContentSnippet;
|
|
417
|
+
this.postThumbnailUrl = data.postThumbnailUrl;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -102,3 +102,27 @@ test('SchedulingPostStatus gains PUBLISHING (AD2-1153 publish resilience)', () =
|
|
|
102
102
|
const post = basePost({ status: SchedulingPostStatus.PUBLISHING });
|
|
103
103
|
assert.equal(post.status, 'publishing');
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
// AD2-1154 FR-2 — the backend's local PlatformStatusMap already has an
|
|
107
|
+
// 'unknown' member (a platform Zernio attempted but couldn't yet confirm
|
|
108
|
+
// either way — Paweł's Facebook incident); this pins the lib side so the
|
|
109
|
+
// backend's `platformStatuses: (...) as unknown as SchedulingPostDTO['platformStatuses']`
|
|
110
|
+
// cast in post-response.dto.ts can eventually be replaced by a direct import.
|
|
111
|
+
test("SchedulingPlatformStatusMap gains 'unknown' (AD2-1154 FR-2 honest reconcile state)", () => {
|
|
112
|
+
const post = basePost({ platformStatuses: { instagram: 'published', facebook: 'unknown' } });
|
|
113
|
+
assert.equal(post.platformStatuses.facebook, 'unknown');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// AD2-1154 FR-3a — the backend's local platform_retry_counts column already
|
|
117
|
+
// tracks per-platform attempts (a permanently-failing platform must never
|
|
118
|
+
// loop forever); this pins the lib side so the field can be exposed on the
|
|
119
|
+
// DTO without a cast once the backend swaps to it.
|
|
120
|
+
test('SchedulingPostDTO gains platformRetryCounts (AD2-1154 FR-3a terminal-retry tracking)', () => {
|
|
121
|
+
const post = basePost({ platformRetryCounts: { tiktok: 3 } });
|
|
122
|
+
assert.equal(post.platformRetryCounts.tiktok, 3);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('SchedulingPostDTO platformRetryCounts is absent by default (backward compatible)', () => {
|
|
126
|
+
const post = basePost();
|
|
127
|
+
assert.equal(post.platformRetryCounts, undefined);
|
|
128
|
+
});
|
|
@@ -27,7 +27,12 @@ export enum SchedulingPostStatus {
|
|
|
27
27
|
|
|
28
28
|
export type SchedulingPlatformStatusMap = Record<
|
|
29
29
|
string,
|
|
30
|
-
'
|
|
30
|
+
// 'unknown': the publisher attempted this platform but its outcome isn't
|
|
31
|
+
// confirmable yet (e.g. Zernio 'pending'/'publishing') — distinct from
|
|
32
|
+
// 'scheduled' (not yet attempted at all). Must never render as a success
|
|
33
|
+
// or a failure (AD2-1154 FR-2, Paweł's incident: his Facebook post was
|
|
34
|
+
// neither confirmed nor failed).
|
|
35
|
+
'scheduled' | 'published' | 'failed' | 'unknown'
|
|
31
36
|
>;
|
|
32
37
|
|
|
33
38
|
// ── SchedulingPlatformTargetDTO ───────────────────────────────────────────────
|
|
@@ -163,6 +168,12 @@ export class SchedulingPostDTO {
|
|
|
163
168
|
postUrls?: Record<string, string>;
|
|
164
169
|
/** Per-platform error messages for platforms that failed to publish. */
|
|
165
170
|
platformErrors?: Record<string, string>;
|
|
171
|
+
/**
|
|
172
|
+
* Per-platform retry-attempt counts (AD2-1154 FR-3a). A platform that has
|
|
173
|
+
* used its 3rd attempt and is still failed is terminal — the retry
|
|
174
|
+
* affordance withdraws and the last real error is shown as permanent.
|
|
175
|
+
*/
|
|
176
|
+
platformRetryCounts?: Record<string, number>;
|
|
166
177
|
scheduledAt?: string;
|
|
167
178
|
publishedAt?: string;
|
|
168
179
|
status: SchedulingPostStatus;
|
|
@@ -196,6 +207,7 @@ export class SchedulingPostDTO {
|
|
|
196
207
|
this.platformStatuses = data.platformStatuses;
|
|
197
208
|
this.postUrls = data.postUrls;
|
|
198
209
|
this.platformErrors = data.platformErrors;
|
|
210
|
+
this.platformRetryCounts = data.platformRetryCounts;
|
|
199
211
|
this.scheduledAt = data.scheduledAt;
|
|
200
212
|
this.publishedAt = data.publishedAt;
|
|
201
213
|
this.status = data.status;
|