@cofondateurauchomage/libs 1.1.135 → 1.1.139
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/build/api.d.ts +12 -1
- package/build/api.validate.js +33 -12
- package/build/db.model.d.ts +32 -1
- package/build/db.model.js +1 -0
- package/build/regex.d.ts +6 -0
- package/build/regex.js +37 -1
- package/package.json +1 -1
package/build/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IBlogHtmlArticle, IContact, INewsletter, IProject, IProspect, IReactionStatus, IUser, TPlan, TPlanOption, TVisibility } from "./db.model";
|
|
2
|
-
export type CloudFunctionNames = "createProject" | "updateProject" | "deleteProject" | "createUser" | "updateUser" | "deleteUser" | "updateVisibility" | "addStripeId" | "updatePlan" | "addStatsAssociation" | "updateNewsletter" | "createProspect" | "getProspect" | "updateProspect" | "createReaction" | "deleteReaction" | "createBlogHtmlArticle";
|
|
2
|
+
export type CloudFunctionNames = "createProject" | "updateProject" | "deleteProject" | "createUser" | "updateUser" | "deleteUser" | "updateVisibility" | "addStripeId" | "updatePlan" | "updateMetaStripe" | "addStatsAssociation" | "updateNewsletter" | "createProspect" | "getProspect" | "updateProspect" | "createReaction" | "deleteReaction" | "createBlogHtmlArticle";
|
|
3
3
|
export type RouteNames = "checkout_session" | "delete_customer" | "portal_session" | "webhook";
|
|
4
4
|
export type BodyForO<O extends CloudFunctionNames | RouteNames> = {
|
|
5
5
|
createProject: Omit<IProject, "id" | "plan" | "visibility" | "clicks" | "creationDate" | "lastConnection" | "stripeId" | "referrer" | "postedOnLinkedInAt"> & IContact & {
|
|
@@ -28,6 +28,16 @@ export type BodyForO<O extends CloudFunctionNames | RouteNames> = {
|
|
|
28
28
|
email: string;
|
|
29
29
|
plan: TPlan;
|
|
30
30
|
};
|
|
31
|
+
updateMetaStripe: {
|
|
32
|
+
stripeId: string;
|
|
33
|
+
stripeFirstPaidAt?: number;
|
|
34
|
+
stripeLastPaymentAt?: number;
|
|
35
|
+
stripeSubscriptionEndedAt?: number | null;
|
|
36
|
+
stripeCurrentPeriodEnd?: number | null;
|
|
37
|
+
stripeNetSpendDeltaCents?: number;
|
|
38
|
+
subscriptionCancellationFeedback?: string | null;
|
|
39
|
+
subscriptionCancellationComment?: string | null;
|
|
40
|
+
};
|
|
31
41
|
addStatsAssociation: {};
|
|
32
42
|
updateNewsletter: Omit<INewsletter, "new_profil_last_sent_date">;
|
|
33
43
|
createProspect: Pick<IProspect, "email" | "referrer">;
|
|
@@ -79,6 +89,7 @@ export type ResponseForO<O extends CloudFunctionNames | RouteNames> = {
|
|
|
79
89
|
updateVisibility: WriteResult;
|
|
80
90
|
addStripeId: WriteResult;
|
|
81
91
|
updatePlan: WriteResult;
|
|
92
|
+
updateMetaStripe: WriteResult;
|
|
82
93
|
addStatsAssociation: WriteResult;
|
|
83
94
|
updateNewsletter: WriteResult;
|
|
84
95
|
createProspect: {
|
package/build/api.validate.js
CHANGED
|
@@ -22,21 +22,33 @@ const zVisibility = zod_1.z.enum(const_1.VISIBILITIES);
|
|
|
22
22
|
const zName = zod_1.z.string().regex(regex_1.RGX.Name.regex);
|
|
23
23
|
const zStrMax50 = zod_1.z.string().max(50);
|
|
24
24
|
const zStrMax1000 = zod_1.z.string().max(1000);
|
|
25
|
-
const zLinkedin = zod_1.z
|
|
25
|
+
const zLinkedin = zod_1.z
|
|
26
|
+
.string()
|
|
27
|
+
.refine((s) => (0, regex_1.normalizeLinkedInUrl)(s) !== null, {
|
|
28
|
+
message: regex_1.RGX.Linkedin.message,
|
|
29
|
+
})
|
|
30
|
+
.transform((s) => (0, regex_1.normalizeLinkedInUrl)(s));
|
|
26
31
|
const zTel = zod_1.z.string().regex(regex_1.RGX.Tel.regex);
|
|
32
|
+
/**
|
|
33
|
+
* Optionnel sur un schéma string qui rejette `""` (ex. `.regex()` ou contrainte équivalente).
|
|
34
|
+
* Les simples `z.string()` / `z.string().max()` acceptent déjà la chaîne vide.
|
|
35
|
+
*/
|
|
36
|
+
function optionalStr(schema) {
|
|
37
|
+
return zod_1.z.union([schema, zod_1.z.literal("")]).optional();
|
|
38
|
+
}
|
|
27
39
|
const zSkillsRequired = zod_1.z.array(zSkill).min(1).max(2);
|
|
28
40
|
const zSkillsOptional = zod_1.z.array(zSkill).min(1).max(2).optional();
|
|
29
41
|
const contactCreate = {
|
|
30
42
|
email: zod_1.z.string(),
|
|
31
43
|
emailProspect: zod_1.z.string(),
|
|
32
|
-
tel: zTel
|
|
33
|
-
linkedin: zLinkedin
|
|
44
|
+
tel: optionalStr(zTel),
|
|
45
|
+
linkedin: optionalStr(zLinkedin),
|
|
34
46
|
website: zod_1.z.string().optional(),
|
|
35
47
|
newsletterMarketing: zod_1.z.boolean().optional(),
|
|
36
48
|
};
|
|
37
49
|
const contactUpdate = {
|
|
38
|
-
tel: zTel
|
|
39
|
-
linkedin: zLinkedin
|
|
50
|
+
tel: optionalStr(zTel),
|
|
51
|
+
linkedin: optionalStr(zLinkedin),
|
|
40
52
|
website: zod_1.z.string().optional(),
|
|
41
53
|
};
|
|
42
54
|
const projectBody = {
|
|
@@ -153,6 +165,14 @@ const schemasForAllRoutes = {
|
|
|
153
165
|
email: zod_1.z.string(),
|
|
154
166
|
plan: zod_1.z.enum(["free", "pro", "premium"]),
|
|
155
167
|
}),
|
|
168
|
+
updateMetaStripe: zod_1.z.object({
|
|
169
|
+
stripeId: zStrMax50,
|
|
170
|
+
stripeFirstPaidAt: zod_1.z.number().optional(),
|
|
171
|
+
stripeLastPaymentAt: zod_1.z.number().optional(),
|
|
172
|
+
stripeSubscriptionEndedAt: zod_1.z.union([zod_1.z.number(), zod_1.z.null()]).optional(),
|
|
173
|
+
stripeCurrentPeriodEnd: zod_1.z.union([zod_1.z.number(), zod_1.z.null()]).optional(),
|
|
174
|
+
stripeNetSpendDeltaCents: zod_1.z.number().int().optional(),
|
|
175
|
+
}),
|
|
156
176
|
addStatsAssociation: zod_1.z.object({}),
|
|
157
177
|
updateNewsletter: zod_1.z.object({
|
|
158
178
|
match: zod_1.z.boolean().optional(),
|
|
@@ -170,8 +190,8 @@ const schemasForAllRoutes = {
|
|
|
170
190
|
}),
|
|
171
191
|
updateProspect: zod_1.z.object({
|
|
172
192
|
name: zStrMax50.optional(),
|
|
173
|
-
lastName: zName
|
|
174
|
-
firstName: zName
|
|
193
|
+
lastName: optionalStr(zName),
|
|
194
|
+
firstName: optionalStr(zName),
|
|
175
195
|
description: zStrMax1000.optional(),
|
|
176
196
|
status: zProjectStatus.optional(),
|
|
177
197
|
status_detail: zStrMax1000.optional(),
|
|
@@ -182,7 +202,7 @@ const schemasForAllRoutes = {
|
|
|
182
202
|
skills: zSkillsOptional,
|
|
183
203
|
yearsOfExperience: zod_1.z.number().min(0).max(100).optional(),
|
|
184
204
|
availability: zAvailabilityWithNeverMind.optional(),
|
|
185
|
-
city: zName
|
|
205
|
+
city: optionalStr(zName),
|
|
186
206
|
invest: zod_1.z.number().min(0).max(1_000_000).optional(),
|
|
187
207
|
partner: zStrMax1000.optional(),
|
|
188
208
|
partner_swanbase: zod_1.z.boolean().optional(),
|
|
@@ -192,8 +212,8 @@ const schemasForAllRoutes = {
|
|
|
192
212
|
bestStrength: zStrMax1000.optional(),
|
|
193
213
|
projectStatus: zod_1.z.array(zProjectStatusWithNeverMind).optional(),
|
|
194
214
|
email: zod_1.z.string(),
|
|
195
|
-
tel: zTel
|
|
196
|
-
linkedin: zLinkedin
|
|
215
|
+
tel: optionalStr(zTel),
|
|
216
|
+
linkedin: optionalStr(zLinkedin),
|
|
197
217
|
website: zod_1.z.string().optional(),
|
|
198
218
|
}),
|
|
199
219
|
createReaction: zod_1.z.object({
|
|
@@ -246,10 +266,11 @@ function validateBodyForO(route, body) {
|
|
|
246
266
|
throwFromZodError(parsed.error);
|
|
247
267
|
}
|
|
248
268
|
const input = body;
|
|
269
|
+
const data = parsed.data;
|
|
249
270
|
const result = {};
|
|
250
|
-
for (const key of Object.keys(
|
|
271
|
+
for (const key of Object.keys(data)) {
|
|
251
272
|
if (input[key] !== undefined) {
|
|
252
|
-
result[key] =
|
|
273
|
+
result[key] = data[key];
|
|
253
274
|
}
|
|
254
275
|
}
|
|
255
276
|
return result;
|
package/build/db.model.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export declare enum Collections {
|
|
|
8
8
|
prospects = "prospects",
|
|
9
9
|
reactions = "reactions",
|
|
10
10
|
visits = "visits",
|
|
11
|
-
blogHtmlArticles = "blog_html_articles"
|
|
11
|
+
blogHtmlArticles = "blog_html_articles",
|
|
12
|
+
crm_profiles = "crm_profiles"
|
|
12
13
|
}
|
|
13
14
|
export type TSkill = "Sales" | "Operation" | "Design" | "Marketing" | "Produit" | "Tech" | "Growth" | "Finance";
|
|
14
15
|
export type TPlan = "free" | "pro" | "premium";
|
|
@@ -82,6 +83,36 @@ export interface IProject {
|
|
|
82
83
|
partner_swanbase?: boolean;
|
|
83
84
|
partner_iii?: boolean;
|
|
84
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* CRM-side state: team actions, Stripe sync, billing overlay.
|
|
88
|
+
* Document id = canonicalId: `user:{uid}` | `project:{uid}` | `prospect:{docId}` under collection `crm_profiles`.
|
|
89
|
+
*/
|
|
90
|
+
export interface ICrmProfile {
|
|
91
|
+
lastTeamCallAt?: number;
|
|
92
|
+
foundPartner?: boolean;
|
|
93
|
+
/** Stripe infos */
|
|
94
|
+
/** First successful invoice payment tied to subscriptions (unix ms). */
|
|
95
|
+
stripeFirstPaidAt?: number;
|
|
96
|
+
/** Last successful invoice payment (unix ms). */
|
|
97
|
+
stripeLastPaymentAt?: number;
|
|
98
|
+
/** When the subscription last ended/churned (unix ms); cleared when a new subscription starts. */
|
|
99
|
+
stripeSubscriptionEndedAt?: number;
|
|
100
|
+
/** Stripe `current_period_end` for the active subscription (unix ms); cleared when no active sub. */
|
|
101
|
+
stripeCurrentPeriodEnd?: number;
|
|
102
|
+
/** Lifetime net cents (invoice payments − refunds handled via deltas from webhooks). */
|
|
103
|
+
stripeNetSpendCents?: number;
|
|
104
|
+
/** Stripe `cancellation_details.feedback`. */
|
|
105
|
+
subscriptionCancellationFeedback?: string;
|
|
106
|
+
subscriptionCancellationComment?: string;
|
|
107
|
+
/** Team-granted Pro (unix ms); orthogonal to Stripe. */
|
|
108
|
+
complimentaryProGrantedAt?: number;
|
|
109
|
+
complimentaryProRevokedAt?: number;
|
|
110
|
+
}
|
|
111
|
+
export interface ICrmNote {
|
|
112
|
+
text: string;
|
|
113
|
+
createdAt: number;
|
|
114
|
+
author: string;
|
|
115
|
+
}
|
|
85
116
|
export interface IContact {
|
|
86
117
|
email: string;
|
|
87
118
|
linkedin?: string;
|
package/build/db.model.js
CHANGED
|
@@ -13,4 +13,5 @@ var Collections;
|
|
|
13
13
|
Collections["reactions"] = "reactions";
|
|
14
14
|
Collections["visits"] = "visits";
|
|
15
15
|
Collections["blogHtmlArticles"] = "blog_html_articles";
|
|
16
|
+
Collections["crm_profiles"] = "crm_profiles";
|
|
16
17
|
})(Collections || (exports.Collections = Collections = {}));
|
package/build/regex.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accepte plusieurs formes d’URL profil LinkedIn et renvoie toujours
|
|
3
|
+
* `https://www.linkedin.com/.../` (slash final, sans query/hash).
|
|
4
|
+
* Retourne `null` si l’URL n’est pas un profil LinkedIn valide.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeLinkedInUrl(raw: string): string | null;
|
|
1
7
|
export declare const RGX: {
|
|
2
8
|
Date: {
|
|
3
9
|
regex: RegExp;
|
package/build/regex.js
CHANGED
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RGX = void 0;
|
|
4
|
+
exports.normalizeLinkedInUrl = normalizeLinkedInUrl;
|
|
4
5
|
const regex = {
|
|
5
6
|
date: /^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))$/,
|
|
6
7
|
email: /^[\w.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
|
|
7
|
-
|
|
8
|
+
/** Saisie tolérante (schéma / attribut HTML). La valeur canonique est produite par `normalizeLinkedInUrl`. */
|
|
9
|
+
linkedin: /^(https?:\/\/)?(www\.)?([\w-]+\.)?linkedin\.com\/(pub|in|profile)\/.+$/i,
|
|
8
10
|
name: /^(?=.*[A-Za-zÜ-ü])[A-Za-zÜ-ü\s\-]{1,50}$/,
|
|
9
11
|
tel: /^([+][1-9]{2,3}[ .\-]?)?[0-9]{1,3}([ .\-]?[0-9]{2,3}){3,6}$/,
|
|
10
12
|
};
|
|
11
13
|
const pattern = (regex) => String(regex).replace("/^", "").replace("$/", "");
|
|
14
|
+
/**
|
|
15
|
+
* Accepte plusieurs formes d’URL profil LinkedIn et renvoie toujours
|
|
16
|
+
* `https://www.linkedin.com/.../` (slash final, sans query/hash).
|
|
17
|
+
* Retourne `null` si l’URL n’est pas un profil LinkedIn valide.
|
|
18
|
+
*/
|
|
19
|
+
function normalizeLinkedInUrl(raw) {
|
|
20
|
+
const trimmed = raw.trim();
|
|
21
|
+
if (!trimmed)
|
|
22
|
+
return null;
|
|
23
|
+
let href = trimmed;
|
|
24
|
+
if (!/^https?:\/\//i.test(href)) {
|
|
25
|
+
href = `https://${href}`;
|
|
26
|
+
}
|
|
27
|
+
let url;
|
|
28
|
+
try {
|
|
29
|
+
url = new URL(href);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const hn = url.hostname.toLowerCase();
|
|
35
|
+
const validHost = hn === "linkedin.com" ||
|
|
36
|
+
hn === "www.linkedin.com" ||
|
|
37
|
+
/^[\w-]+\.linkedin\.com$/i.test(hn);
|
|
38
|
+
if (!validHost)
|
|
39
|
+
return null;
|
|
40
|
+
if (!/^\/(in|pub|profile)\/.+/i.test(url.pathname)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const path = url.pathname.endsWith("/")
|
|
44
|
+
? url.pathname
|
|
45
|
+
: `${url.pathname}/`;
|
|
46
|
+
return `https://www.linkedin.com${path}`;
|
|
47
|
+
}
|
|
12
48
|
exports.RGX = {
|
|
13
49
|
Date: {
|
|
14
50
|
regex: regex.date,
|