ad2app-lib 1.7.0 → 1.9.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/CHANGELOG.md +43 -0
- package/dist/types/I_User.d.ts +5 -0
- package/dist/types/I_User.js +19 -1
- package/package.json +6 -2
- package/src/analytics/index.test.ts +51 -0
- package/src/analytics/index.ts +41 -7
- package/src/api/apiDriver.test.ts +83 -0
- package/src/api/apiDriver.ts +18 -3
- package/src/types/I_User.ts +15 -1
- package/tsconfig.json +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `ad2app-lib` are documented here. The analytics taxonomy
|
|
4
|
+
is the single source of truth across all surfaces; the matching spec lives in
|
|
5
|
+
`ad2appus/docs/posthog-implementation.md` (§3).
|
|
6
|
+
|
|
7
|
+
## 1.9.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Free Tier analytics (epic AD2-887):
|
|
12
|
+
- `EVENTS.FREE_SKILLS_REQUESTED` (`free_skills_requested`), shape `{ source: string }` — email submitted for the free skills (AD2-889).
|
|
13
|
+
- `EVENTS.ACCOUNT_CONNECT_BLOCKED` (`account_connect_blocked`), shape `{ platform?: string }` — free user hits the connect wall (AD2-892).
|
|
14
|
+
- `EVENTS.MCP_POST_BLOCKED_FREE_TIER` (`mcp_post_blocked_free_tier`), shape `{ target_count?: number }` — free user tries to post in the MCP (AD2-898).
|
|
15
|
+
- `'free_skills'` added to the `LANDING_CTA_CLICKED` location union.
|
|
16
|
+
- Email lifecycle events (Resend webhook -> PostHog, AD2-894):
|
|
17
|
+
`EVENTS.EMAIL_DELIVERED` / `EMAIL_OPENED` / `EMAIL_CLICKED` / `EMAIL_BOUNCED` /
|
|
18
|
+
`EMAIL_COMPLAINED`, all sharing `EmailEventProperties` (`{ email_id?, subject?, link? }`).
|
|
19
|
+
|
|
20
|
+
## 1.8.0
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `EVENTS.CONNECT_ARTIFACT_COPIED` (`connect_artifact_copied`) with property
|
|
25
|
+
shape `{ target: 'app' | 'ask' | 'terminal' | 'editor' }` — fired when the
|
|
26
|
+
`/connect` install snippet is copied.
|
|
27
|
+
- `'ai_connector'` added to the `LANDING_CTA_CLICKED` location union
|
|
28
|
+
(`hero | pricing | final_cta | nav | ai_connector`).
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
|
|
32
|
+
- `EVENTS.TRIAL_STARTED` (`trial_started`) and its `EventProperties` shape.
|
|
33
|
+
- `PERSON_PROPS.TRIAL_ENDS_AT` (`trial_ends_at`).
|
|
34
|
+
- `'trial'` removed from the documented `PLAN` person-property values, which now
|
|
35
|
+
match the canonical `SchedulingSubscriptionTier` (`free | starter | pro`).
|
|
36
|
+
|
|
37
|
+
ad2app is a paid-only product with no trial, so these events never fired.
|
|
38
|
+
Verified that no consumer (frontend, backend, landing, mcp) referenced them.
|
|
39
|
+
|
|
40
|
+
> **Semver note:** removing exported symbols is technically breaking. It is
|
|
41
|
+
> released as a minor because no consumer referenced the removed tokens and all
|
|
42
|
+
> consumers pin `^1.7.x`, so they upgrade frictionlessly. If you prefer strict
|
|
43
|
+
> semver, retag as `2.0.0` and bump the consumer ranges deliberately.
|
package/dist/types/I_User.d.ts
CHANGED
package/dist/types/I_User.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.I_UserAuthenticateDTO = exports.I_UserFindOneDTO = exports.I_UserSignUpDTO = exports.I_UserSignInDTO = exports.I_UserCreateDTO = exports.UserRoles = exports.I_SignedUser = exports.I_User = void 0;
|
|
12
|
+
exports.I_WebSocialSignInDTO = exports.I_UserAuthenticateDTO = exports.I_UserFindOneDTO = exports.I_UserSignUpDTO = exports.I_UserSignInDTO = exports.I_UserCreateDTO = exports.UserRoles = exports.I_SignedUser = exports.I_User = void 0;
|
|
13
13
|
const class_validator_1 = require("class-validator");
|
|
14
14
|
class I_User {
|
|
15
15
|
}
|
|
@@ -90,3 +90,21 @@ __decorate([
|
|
|
90
90
|
(0, class_validator_1.IsNotEmpty)(),
|
|
91
91
|
__metadata("design:type", String)
|
|
92
92
|
], I_UserAuthenticateDTO.prototype, "accessToken", void 0);
|
|
93
|
+
class I_WebSocialSignInDTO {
|
|
94
|
+
}
|
|
95
|
+
exports.I_WebSocialSignInDTO = I_WebSocialSignInDTO;
|
|
96
|
+
__decorate([
|
|
97
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
98
|
+
(0, class_validator_1.IsString)(),
|
|
99
|
+
__metadata("design:type", String)
|
|
100
|
+
], I_WebSocialSignInDTO.prototype, "idToken", void 0);
|
|
101
|
+
__decorate([
|
|
102
|
+
(0, class_validator_1.IsOptional)(),
|
|
103
|
+
(0, class_validator_1.IsBoolean)(),
|
|
104
|
+
__metadata("design:type", Boolean)
|
|
105
|
+
], I_WebSocialSignInDTO.prototype, "termsAccepted", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
(0, class_validator_1.IsOptional)(),
|
|
108
|
+
(0, class_validator_1.IsBoolean)(),
|
|
109
|
+
__metadata("design:type", Boolean)
|
|
110
|
+
], I_WebSocialSignInDTO.prototype, "privacyPolicyAccepted", void 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ad2app-lib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"build": "npx tsc"
|
|
37
|
+
"build": "npx tsc",
|
|
38
|
+
"test": "node --import tsx --test $(find src -name '*.test.ts')"
|
|
38
39
|
},
|
|
39
40
|
"keywords": [],
|
|
40
41
|
"author": "Maciej Górski@ad2.app",
|
|
@@ -45,5 +46,8 @@
|
|
|
45
46
|
"date-fns": "^3.5.0",
|
|
46
47
|
"date-fns-tz": "^3.2.0",
|
|
47
48
|
"typescript": "^5.8.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"tsx": "^4.22.4"
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract tests for the canonical PostHog analytics taxonomy. These lock the
|
|
3
|
+
* wire format every emitter (frontend / backend / landing / MCP) depends on:
|
|
4
|
+
* renames do not migrate historical data, so an accidental rename or typo must
|
|
5
|
+
* fail the build, not silently break dashboards. Written as structural
|
|
6
|
+
* invariants so legitimately ADDING new events stays green (AD2-952).
|
|
7
|
+
*/
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { test } from "node:test";
|
|
10
|
+
|
|
11
|
+
import { EVENTS, PERSON_PROPS } from "./index";
|
|
12
|
+
|
|
13
|
+
const SNAKE_CASE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
|
|
14
|
+
|
|
15
|
+
test("every EVENTS value is a unique, non-empty snake_case string", () => {
|
|
16
|
+
const values = Object.values(EVENTS);
|
|
17
|
+
assert.equal(new Set(values).size, values.length, "duplicate event name");
|
|
18
|
+
for (const value of values) assert.match(value, SNAKE_CASE, `not snake_case: ${value}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("every EVENTS value equals the lower-case of its key (no copy-paste drift)", () => {
|
|
22
|
+
// Convention: KEY is SCREAMING_SNAKE, value is its lower-case wire name. This
|
|
23
|
+
// auto-enforces correct naming for current AND future events.
|
|
24
|
+
for (const [key, value] of Object.entries(EVENTS)) {
|
|
25
|
+
assert.equal(value, key.toLowerCase(), `EVENTS.${key} should be '${key.toLowerCase()}', got '${value}'`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("every PERSON_PROPS value is a unique, non-empty snake_case string", () => {
|
|
30
|
+
const values = Object.values(PERSON_PROPS);
|
|
31
|
+
assert.equal(new Set(values).size, values.length, "duplicate person prop");
|
|
32
|
+
for (const value of values) assert.match(value, SNAKE_CASE, `not snake_case: ${value}`);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("critical event wire names are locked (renaming breaks historical data)", () => {
|
|
36
|
+
// Core funnel + revenue events: a rename silently breaks every dashboard.
|
|
37
|
+
assert.equal(EVENTS.SIGNED_UP, "signed_up");
|
|
38
|
+
assert.equal(EVENTS.LOGGED_IN, "logged_in");
|
|
39
|
+
assert.equal(EVENTS.SOCIAL_ACCOUNT_CONNECTED, "social_account_connected");
|
|
40
|
+
assert.equal(EVENTS.POST_PUBLISHED, "post_published");
|
|
41
|
+
assert.equal(EVENTS.LANDING_CTA_CLICKED, "landing_cta_clicked");
|
|
42
|
+
assert.equal(EVENTS.CHECKOUT_STARTED, "checkout_started");
|
|
43
|
+
assert.equal(EVENTS.SUBSCRIPTION_STARTED, "subscription_started");
|
|
44
|
+
assert.equal(EVENTS.SUBSCRIPTION_CANCELED, "subscription_canceled");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("critical person property keys are locked", () => {
|
|
48
|
+
assert.equal(PERSON_PROPS.EMAIL, "email");
|
|
49
|
+
assert.equal(PERSON_PROPS.PLAN, "plan");
|
|
50
|
+
assert.equal(PERSON_PROPS.ROLE, "role");
|
|
51
|
+
});
|
package/src/analytics/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Single source of truth for event names, event property shapes, and person
|
|
5
5
|
* property keys across ALL surfaces (frontend, backend, landing, future MCP).
|
|
6
|
-
* Spec:
|
|
6
|
+
* Spec: ad2app-specs/analytics-posthog/posthog-implementation.md §3.
|
|
7
7
|
*
|
|
8
8
|
* Naming convention: snake_case, object_verb_past_tense.
|
|
9
9
|
* Do NOT rename events after they ship — historical data does not migrate.
|
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
export const EVENTS = {
|
|
14
14
|
// Acquisition (landing)
|
|
15
15
|
LANDING_CTA_CLICKED: 'landing_cta_clicked',
|
|
16
|
+
CONNECT_ARTIFACT_COPIED: 'connect_artifact_copied', // /connect install-snippet copy
|
|
17
|
+
|
|
18
|
+
// Free Tier (skills lead magnet + connect wall)
|
|
19
|
+
FREE_SKILLS_REQUESTED: 'free_skills_requested', // email submitted for the free skills (AD2-889)
|
|
20
|
+
ACCOUNT_CONNECT_BLOCKED: 'account_connect_blocked', // free user hits the connect wall (AD2-892)
|
|
21
|
+
MCP_POST_BLOCKED_FREE_TIER: 'mcp_post_blocked_free_tier', // free user tries to post in the MCP (AD2-898)
|
|
22
|
+
|
|
23
|
+
// Email lifecycle (Resend webhook -> PostHog, AD2-894)
|
|
24
|
+
EMAIL_DELIVERED: 'email_delivered',
|
|
25
|
+
EMAIL_OPENED: 'email_opened',
|
|
26
|
+
EMAIL_CLICKED: 'email_clicked',
|
|
27
|
+
EMAIL_BOUNCED: 'email_bounced',
|
|
28
|
+
EMAIL_COMPLAINED: 'email_complained',
|
|
16
29
|
|
|
17
30
|
// Activation (web app)
|
|
18
31
|
SIGNED_UP: 'signed_up', // server-owned (backend, on user creation)
|
|
@@ -24,8 +37,7 @@ export const EVENTS = {
|
|
|
24
37
|
POST_PUBLISHED: 'post_published', // server-confirmed (backend)
|
|
25
38
|
ANALYTICS_VIEWED: 'analytics_viewed',
|
|
26
39
|
|
|
27
|
-
// Revenue (backend = source of truth)
|
|
28
|
-
TRIAL_STARTED: 'trial_started',
|
|
40
|
+
// Revenue (backend = source of truth). Paid-only product: no trial.
|
|
29
41
|
LIMIT_REACHED: 'limit_reached', // cap hit (e.g. X link-post cap) — the upsell trigger
|
|
30
42
|
UPGRADE_CLICKED: 'upgrade_clicked', // client intent
|
|
31
43
|
CHECKOUT_STARTED: 'checkout_started',
|
|
@@ -35,11 +47,35 @@ export const EVENTS = {
|
|
|
35
47
|
|
|
36
48
|
export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
37
49
|
|
|
50
|
+
/** Shared shape for the Resend email lifecycle events (AD2-894). */
|
|
51
|
+
export interface EmailEventProperties {
|
|
52
|
+
email_id?: string;
|
|
53
|
+
subject?: string;
|
|
54
|
+
link?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
/** Property shape per event. Keeps emitters honest across repos. */
|
|
39
58
|
export interface EventProperties {
|
|
40
59
|
[EVENTS.LANDING_CTA_CLICKED]: {
|
|
41
|
-
location:
|
|
60
|
+
location:
|
|
61
|
+
| 'hero'
|
|
62
|
+
| 'pricing'
|
|
63
|
+
| 'final_cta'
|
|
64
|
+
| 'nav'
|
|
65
|
+
| 'ai_connector'
|
|
66
|
+
| 'free_skills';
|
|
67
|
+
};
|
|
68
|
+
[EVENTS.CONNECT_ARTIFACT_COPIED]: {
|
|
69
|
+
target: 'app' | 'ask' | 'terminal' | 'editor';
|
|
42
70
|
};
|
|
71
|
+
[EVENTS.FREE_SKILLS_REQUESTED]: { source: string };
|
|
72
|
+
[EVENTS.ACCOUNT_CONNECT_BLOCKED]: { platform?: string };
|
|
73
|
+
[EVENTS.MCP_POST_BLOCKED_FREE_TIER]: { target_count?: number };
|
|
74
|
+
[EVENTS.EMAIL_DELIVERED]: EmailEventProperties;
|
|
75
|
+
[EVENTS.EMAIL_OPENED]: EmailEventProperties;
|
|
76
|
+
[EVENTS.EMAIL_CLICKED]: EmailEventProperties;
|
|
77
|
+
[EVENTS.EMAIL_BOUNCED]: EmailEventProperties;
|
|
78
|
+
[EVENTS.EMAIL_COMPLAINED]: EmailEventProperties;
|
|
43
79
|
[EVENTS.SIGNED_UP]: { method: 'email' | 'google'; role: string };
|
|
44
80
|
[EVENTS.PROFILE_COMPLETED]: { role: string };
|
|
45
81
|
[EVENTS.LOGGED_IN]: { method?: 'email' | 'google' };
|
|
@@ -59,7 +95,6 @@ export interface EventProperties {
|
|
|
59
95
|
status: 'success' | 'partial' | 'failed';
|
|
60
96
|
};
|
|
61
97
|
[EVENTS.ANALYTICS_VIEWED]: { scope?: 'overview' | 'account' | 'post' };
|
|
62
|
-
[EVENTS.TRIAL_STARTED]: { plan: string };
|
|
63
98
|
[EVENTS.LIMIT_REACHED]: {
|
|
64
99
|
limit: 'x_link_posts' | 'accounts';
|
|
65
100
|
plan: string;
|
|
@@ -83,9 +118,8 @@ export interface EventProperties {
|
|
|
83
118
|
export const PERSON_PROPS = {
|
|
84
119
|
EMAIL: 'email',
|
|
85
120
|
ROLE: 'role',
|
|
86
|
-
PLAN: 'plan', // '
|
|
121
|
+
PLAN: 'plan', // 'free' | 'starter' | 'pro' (SchedulingSubscriptionTier)
|
|
87
122
|
ACCOUNT_COUNT: 'account_count',
|
|
88
|
-
TRIAL_ENDS_AT: 'trial_ends_at',
|
|
89
123
|
} as const;
|
|
90
124
|
|
|
91
125
|
export type PersonPropKey = (typeof PERSON_PROPS)[keyof typeof PERSON_PROPS];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the shared HTTP layer (apiDriver / fetchCall). Mocks global
|
|
3
|
+
* fetch. Covers the two bugs fixed in AD2-947: PUT dropping baseUrl, and the
|
|
4
|
+
* broken non-JSON / error-body response handling.
|
|
5
|
+
*/
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { afterEach, beforeEach, test } from "node:test";
|
|
8
|
+
|
|
9
|
+
import { apiDriver, configureApiDriver, fetchCall } from "./apiDriver";
|
|
10
|
+
|
|
11
|
+
configureApiDriver({ apiUrl: "https://default.test" });
|
|
12
|
+
|
|
13
|
+
let calls: { url: string }[] = [];
|
|
14
|
+
let body: string | null = JSON.stringify({ ok: true });
|
|
15
|
+
let status = 200;
|
|
16
|
+
const realFetch = globalThis.fetch;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
calls = [];
|
|
20
|
+
body = JSON.stringify({ ok: true });
|
|
21
|
+
status = 200;
|
|
22
|
+
// Fresh Response per call — a body can only be read once.
|
|
23
|
+
globalThis.fetch = (async (url: unknown) => {
|
|
24
|
+
calls.push({ url: String(url) });
|
|
25
|
+
return new Response(body, { status });
|
|
26
|
+
}) as typeof fetch;
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
globalThis.fetch = realFetch;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const VERBS = ["get", "post", "put", "patch", "delete"] as const;
|
|
33
|
+
|
|
34
|
+
test("every verb forwards the driver baseUrl (regression: PUT used to drop it)", async () => {
|
|
35
|
+
const api = apiDriver("https://other.test");
|
|
36
|
+
for (const verb of VERBS) {
|
|
37
|
+
calls = [];
|
|
38
|
+
await (api[verb] as (path: string) => Promise<unknown>)("thing");
|
|
39
|
+
assert.ok(
|
|
40
|
+
calls[0]?.url.startsWith("https://other.test/thing"),
|
|
41
|
+
`${verb} should target other.test, got ${calls[0]?.url}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("PUT to a non-default baseUrl does not fall back to CONFIG.apiUrl", async () => {
|
|
47
|
+
await apiDriver("https://other.test").put("x");
|
|
48
|
+
assert.ok(
|
|
49
|
+
calls[0]?.url.startsWith("https://other.test/"),
|
|
50
|
+
`PUT routed to ${calls[0]?.url}`
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("fetchCall parses a JSON 2xx body", async () => {
|
|
55
|
+
body = JSON.stringify({ a: 1 });
|
|
56
|
+
const result = await fetchCall<{ a: number }, unknown>({ path: "x", method: "get" });
|
|
57
|
+
assert.deepEqual(result, { a: 1 });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("fetchCall returns text for a non-JSON 2xx body (no rejected promise)", async () => {
|
|
61
|
+
body = "plain text";
|
|
62
|
+
const result = await fetchCall<string, unknown>({ path: "x", method: "get" });
|
|
63
|
+
assert.equal(result, "plain text");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("fetchCall returns undefined for an empty 2xx body", async () => {
|
|
67
|
+
body = null;
|
|
68
|
+
status = 204;
|
|
69
|
+
const result = await fetchCall<undefined, unknown>({ path: "x", method: "get" });
|
|
70
|
+
assert.equal(result, undefined);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("fetchCall throws with a plain-text error body (does not assume JSON)", async () => {
|
|
74
|
+
body = "Service Unavailable";
|
|
75
|
+
status = 503;
|
|
76
|
+
await assert.rejects(() => fetchCall({ path: "x", method: "get" }), /Service Unavailable/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("fetchCall surfaces a JSON error body in the thrown message", async () => {
|
|
80
|
+
body = JSON.stringify({ error: "nope" });
|
|
81
|
+
status = 400;
|
|
82
|
+
await assert.rejects(() => fetchCall({ path: "x", method: "get" }), /nope/);
|
|
83
|
+
});
|
package/src/api/apiDriver.ts
CHANGED
|
@@ -65,12 +65,26 @@ export async function fetchCall<T, Q>(args: FetchCallArgs<Q>): Promise<T> {
|
|
|
65
65
|
|
|
66
66
|
const res = await fetch(fullPath, init);
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// Read the body ONCE as text. res.json() returns a promise, so the old
|
|
69
|
+
// synchronous try/catch around it never caught a parse failure (the text()
|
|
70
|
+
// fallback was dead code), and a plain-text error body threw inside res.json().
|
|
71
|
+
const rawBody = await res.text();
|
|
72
|
+
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
let message = rawBody;
|
|
75
|
+
try {
|
|
76
|
+
message = JSON.stringify(JSON.parse(rawBody));
|
|
77
|
+
} catch {
|
|
78
|
+
// non-JSON error body: keep the raw text
|
|
79
|
+
}
|
|
80
|
+
throw new Error(message || `Request failed with status ${res.status}`);
|
|
81
|
+
}
|
|
69
82
|
|
|
83
|
+
if (!rawBody) return undefined as T;
|
|
70
84
|
try {
|
|
71
|
-
return
|
|
85
|
+
return JSON.parse(rawBody) as T;
|
|
72
86
|
} catch {
|
|
73
|
-
return
|
|
87
|
+
return rawBody as unknown as T;
|
|
74
88
|
}
|
|
75
89
|
}
|
|
76
90
|
|
|
@@ -100,6 +114,7 @@ export function apiDriver(baseUrl?: string) {
|
|
|
100
114
|
...args[1],
|
|
101
115
|
path: args[0],
|
|
102
116
|
method: "put",
|
|
117
|
+
baseUrl,
|
|
103
118
|
}),
|
|
104
119
|
patch: <T, Q>(...args: MethodCallArgs<Q>) =>
|
|
105
120
|
fetchCall<T, Q>({
|
package/src/types/I_User.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IsBoolean, IsNotEmpty } from 'class-validator';
|
|
1
|
+
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
|
2
2
|
import { I_Influencer } from './I_Influencer';
|
|
3
3
|
|
|
4
4
|
export class I_User {
|
|
@@ -75,3 +75,17 @@ export class I_UserAuthenticateDTO {
|
|
|
75
75
|
@IsNotEmpty()
|
|
76
76
|
accessToken: string;
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
export class I_WebSocialSignInDTO {
|
|
80
|
+
@IsNotEmpty()
|
|
81
|
+
@IsString()
|
|
82
|
+
idToken: string;
|
|
83
|
+
|
|
84
|
+
@IsOptional()
|
|
85
|
+
@IsBoolean()
|
|
86
|
+
termsAccepted?: boolean;
|
|
87
|
+
|
|
88
|
+
@IsOptional()
|
|
89
|
+
@IsBoolean()
|
|
90
|
+
privacyPolicyAccepted?: boolean;
|
|
91
|
+
}
|