@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 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: {
@@ -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.string().regex(regex_1.RGX.Linkedin.regex);
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.optional(),
33
- linkedin: zLinkedin.optional(),
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.optional(),
39
- linkedin: zLinkedin.optional(),
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.optional(),
174
- firstName: zName.optional(),
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.optional(),
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.optional(),
196
- linkedin: zLinkedin.optional(),
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(schema.shape)) {
271
+ for (const key of Object.keys(data)) {
251
272
  if (input[key] !== undefined) {
252
- result[key] = input[key];
273
+ result[key] = data[key];
253
274
  }
254
275
  }
255
276
  return result;
@@ -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
- linkedin: /^(http(s)?:\/\/)?([\w]+\.)?linkedin\.com\/(pub|in|profile)\/.+$/,
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cofondateurauchomage/libs",
3
- "version": "1.1.135",
3
+ "version": "1.1.139",
4
4
  "description": "",
5
5
  "main": "build/index",
6
6
  "scripts": {