@thejob/schema 2.1.7 → 2.1.9
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/index.cjs +375 -19
- package/dist/index.d.cts +846 -13
- package/dist/index.d.ts +846 -13
- package/dist/index.js +365 -26
- package/package.json +1 -1
- package/src/marketing/marketing.constant.ts +98 -0
- package/src/marketing/marketing.schema.ts +481 -8
- package/src/user/general-detail.schema.ts +7 -0
package/dist/index.cjs
CHANGED
|
@@ -32,8 +32,16 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AnswerChoiceType: () => AnswerChoiceType,
|
|
34
34
|
ApplicationReceivePreference: () => ApplicationReceivePreference,
|
|
35
|
+
BindingKind: () => BindingKind,
|
|
36
|
+
BindingSchema: () => BindingSchema,
|
|
37
|
+
CAMPAIGN_MAX_RECIPIENTS_CEILING: () => CAMPAIGN_MAX_RECIPIENTS_CEILING,
|
|
38
|
+
CampaignAnchorField: () => CampaignAnchorField,
|
|
39
|
+
CampaignEndKind: () => CampaignEndKind,
|
|
40
|
+
CampaignIntervalUnit: () => CampaignIntervalUnit,
|
|
35
41
|
CampaignSchema: () => CampaignSchema,
|
|
36
42
|
CampaignStatus: () => CampaignStatus,
|
|
43
|
+
CampaignTriggerKind: () => CampaignTriggerKind,
|
|
44
|
+
CampaignTriggerSchema: () => CampaignTriggerSchema,
|
|
37
45
|
ChoiceQuestionSchema: () => ChoiceQuestionSchema,
|
|
38
46
|
Common: () => Common,
|
|
39
47
|
CompensationType: () => CompensationType,
|
|
@@ -75,12 +83,15 @@ __export(index_exports, {
|
|
|
75
83
|
JobSearchUrgency: () => JobSearchUrgency,
|
|
76
84
|
JobStatus: () => JobStatus,
|
|
77
85
|
JobSubCategory: () => JobSubCategory,
|
|
86
|
+
ListFilterBranch: () => ListFilterBranch,
|
|
87
|
+
ListFilterBranchSchema: () => ListFilterBranchSchema,
|
|
78
88
|
ListFilterSchema: () => ListFilterSchema,
|
|
79
89
|
LocationSchema: () => LocationSchema,
|
|
80
90
|
MIN_SALARY_LOWER_BOUND: () => MIN_SALARY_LOWER_BOUND,
|
|
81
91
|
MIN_SALARY_UPPER_BOUND: () => MIN_SALARY_UPPER_BOUND,
|
|
82
92
|
MailingListSchema: () => MailingListSchema,
|
|
83
93
|
NotificationChannel: () => NotificationChannel,
|
|
94
|
+
OccurrenceStatus: () => OccurrenceStatus,
|
|
84
95
|
PREFILL_MIRRORED_KEYS: () => PREFILL_MIRRORED_KEYS,
|
|
85
96
|
PRIVATE_PROFILE_FIELDS: () => PRIVATE_PROFILE_FIELDS,
|
|
86
97
|
PageSchema: () => PageSchema,
|
|
@@ -110,7 +121,12 @@ __export(index_exports, {
|
|
|
110
121
|
StudyType: () => StudyType,
|
|
111
122
|
SupportedAnswerChoiceTypes: () => SupportedAnswerChoiceTypes,
|
|
112
123
|
SupportedApplicationReceivePreferences: () => SupportedApplicationReceivePreferences,
|
|
124
|
+
SupportedBindingKinds: () => SupportedBindingKinds,
|
|
125
|
+
SupportedCampaignAnchorFields: () => SupportedCampaignAnchorFields,
|
|
126
|
+
SupportedCampaignEndKinds: () => SupportedCampaignEndKinds,
|
|
127
|
+
SupportedCampaignIntervalUnits: () => SupportedCampaignIntervalUnits,
|
|
113
128
|
SupportedCampaignStatuses: () => SupportedCampaignStatuses,
|
|
129
|
+
SupportedCampaignTriggerKinds: () => SupportedCampaignTriggerKinds,
|
|
114
130
|
SupportedCompensationTypes: () => SupportedCompensationTypes,
|
|
115
131
|
SupportedContactTypes: () => SupportedContactTypes,
|
|
116
132
|
SupportedEducationLevels: () => SupportedEducationLevels,
|
|
@@ -126,6 +142,7 @@ __export(index_exports, {
|
|
|
126
142
|
SupportedJobStatuses: () => SupportedJobStatuses,
|
|
127
143
|
SupportedJobSubCategories: () => SupportedJobSubCategories,
|
|
128
144
|
SupportedNotificationChannels: () => SupportedNotificationChannels,
|
|
145
|
+
SupportedOccurrenceStatuses: () => SupportedOccurrenceStatuses,
|
|
129
146
|
SupportedPageStatuses: () => SupportedPageStatuses,
|
|
130
147
|
SupportedPageTypes: () => SupportedPageTypes,
|
|
131
148
|
SupportedPostStatuses: () => SupportedPostStatuses,
|
|
@@ -2225,10 +2242,79 @@ var PRIVATE_PROFILE_FIELDS = [
|
|
|
2225
2242
|
"additionalInfo"
|
|
2226
2243
|
];
|
|
2227
2244
|
|
|
2245
|
+
// src/marketing/marketing.constant.ts
|
|
2246
|
+
var CampaignStatus = /* @__PURE__ */ ((CampaignStatus2) => {
|
|
2247
|
+
CampaignStatus2["Draft"] = "draft";
|
|
2248
|
+
CampaignStatus2["Sending"] = "sending";
|
|
2249
|
+
CampaignStatus2["Sent"] = "sent";
|
|
2250
|
+
CampaignStatus2["Failed"] = "failed";
|
|
2251
|
+
return CampaignStatus2;
|
|
2252
|
+
})(CampaignStatus || {});
|
|
2253
|
+
var SupportedCampaignStatuses = Object.values(CampaignStatus);
|
|
2254
|
+
var CampaignTriggerKind = /* @__PURE__ */ ((CampaignTriggerKind2) => {
|
|
2255
|
+
CampaignTriggerKind2["Manual"] = "manual";
|
|
2256
|
+
CampaignTriggerKind2["Once"] = "once";
|
|
2257
|
+
CampaignTriggerKind2["Schedule"] = "schedule";
|
|
2258
|
+
CampaignTriggerKind2["Lifecycle"] = "lifecycle";
|
|
2259
|
+
return CampaignTriggerKind2;
|
|
2260
|
+
})(CampaignTriggerKind || {});
|
|
2261
|
+
var SupportedCampaignTriggerKinds = Object.values(CampaignTriggerKind);
|
|
2262
|
+
var CampaignIntervalUnit = /* @__PURE__ */ ((CampaignIntervalUnit2) => {
|
|
2263
|
+
CampaignIntervalUnit2["Day"] = "day";
|
|
2264
|
+
CampaignIntervalUnit2["Week"] = "week";
|
|
2265
|
+
CampaignIntervalUnit2["Month"] = "month";
|
|
2266
|
+
return CampaignIntervalUnit2;
|
|
2267
|
+
})(CampaignIntervalUnit || {});
|
|
2268
|
+
var SupportedCampaignIntervalUnits = Object.values(CampaignIntervalUnit);
|
|
2269
|
+
var CampaignEndKind = /* @__PURE__ */ ((CampaignEndKind2) => {
|
|
2270
|
+
CampaignEndKind2["Never"] = "never";
|
|
2271
|
+
CampaignEndKind2["AfterOccurrences"] = "afterOccurrences";
|
|
2272
|
+
CampaignEndKind2["OnDate"] = "onDate";
|
|
2273
|
+
return CampaignEndKind2;
|
|
2274
|
+
})(CampaignEndKind || {});
|
|
2275
|
+
var SupportedCampaignEndKinds = Object.values(CampaignEndKind);
|
|
2276
|
+
var CampaignAnchorField = /* @__PURE__ */ ((CampaignAnchorField2) => {
|
|
2277
|
+
CampaignAnchorField2["CreatedAt"] = "createdAt";
|
|
2278
|
+
CampaignAnchorField2["OnboardingCompletedAt"] = "onboardingCompletedAt";
|
|
2279
|
+
CampaignAnchorField2["MarketingConsentUpdatedAt"] = "marketingConsent.updatedAt";
|
|
2280
|
+
return CampaignAnchorField2;
|
|
2281
|
+
})(CampaignAnchorField || {});
|
|
2282
|
+
var SupportedCampaignAnchorFields = Object.values(CampaignAnchorField);
|
|
2283
|
+
var BindingKind = /* @__PURE__ */ ((BindingKind2) => {
|
|
2284
|
+
BindingKind2["Static"] = "static";
|
|
2285
|
+
BindingKind2["Recipient"] = "recipient";
|
|
2286
|
+
BindingKind2["Url"] = "url";
|
|
2287
|
+
BindingKind2["Source"] = "source";
|
|
2288
|
+
return BindingKind2;
|
|
2289
|
+
})(BindingKind || {});
|
|
2290
|
+
var SupportedBindingKinds = Object.values(BindingKind);
|
|
2291
|
+
var OccurrenceStatus = /* @__PURE__ */ ((OccurrenceStatus2) => {
|
|
2292
|
+
OccurrenceStatus2["Pending"] = "pending";
|
|
2293
|
+
OccurrenceStatus2["Running"] = "running";
|
|
2294
|
+
OccurrenceStatus2["Sent"] = "sent";
|
|
2295
|
+
OccurrenceStatus2["Skipped"] = "skipped";
|
|
2296
|
+
OccurrenceStatus2["Failed"] = "failed";
|
|
2297
|
+
return OccurrenceStatus2;
|
|
2298
|
+
})(OccurrenceStatus || {});
|
|
2299
|
+
var SupportedOccurrenceStatuses = Object.values(OccurrenceStatus);
|
|
2300
|
+
var CAMPAIGN_MAX_RECIPIENTS_CEILING = 5e3;
|
|
2301
|
+
|
|
2228
2302
|
// src/marketing/marketing.schema.ts
|
|
2229
2303
|
var tokens = (label) => (0, import_yup20.array)().of((0, import_yup20.string)().trim().required()).optional().default([]).label(label);
|
|
2230
2304
|
var enumFacet = (values, label) => (0, import_yup20.array)().of((0, import_yup20.string)().oneOf(values).required()).optional().default([]).label(label);
|
|
2231
|
-
var
|
|
2305
|
+
var days = (label) => (0, import_yup20.number)().integer().min(0).max(3650).optional().default(void 0).label(label);
|
|
2306
|
+
var months = (label) => (0, import_yup20.number)().integer().min(0).max(1200).optional().default(void 0).label(label);
|
|
2307
|
+
var percent = (label) => (0, import_yup20.number)().integer().min(0).max(100).optional().default(void 0).label(label);
|
|
2308
|
+
var epoch = (label) => (0, import_yup20.number)().integer().min(0).optional().default(void 0).label(label);
|
|
2309
|
+
var salaryBound = (label) => (0, import_yup20.number)().integer().min(0).optional().default(void 0).label(label).when("salaryCurrencies", {
|
|
2310
|
+
is: (v) => !Array.isArray(v) || v.length !== 1,
|
|
2311
|
+
then: (s) => s.test(
|
|
2312
|
+
"requires-single-currency",
|
|
2313
|
+
"Pick exactly one salary currency to filter by salary",
|
|
2314
|
+
(value) => value === void 0 || value === null
|
|
2315
|
+
)
|
|
2316
|
+
});
|
|
2317
|
+
var ListFilterBranchSchema = (0, import_yup20.object)({
|
|
2232
2318
|
// ── Enum facets (closed vocabularies, chip pickers) ─────────────────────────
|
|
2233
2319
|
countries: (0, import_yup20.array)().of((0, import_yup20.string)().required()).optional().default([]).label("Countries"),
|
|
2234
2320
|
roles: enumFacet(SupportedUserRoles, "Roles"),
|
|
@@ -2283,39 +2369,285 @@ var ListFilterSchema = (0, import_yup20.object)({
|
|
|
2283
2369
|
// Case-insensitive keyword match against headline + aboutMe.
|
|
2284
2370
|
keywords: tokens("Profile Keywords"),
|
|
2285
2371
|
// ── Range / boolean facets ──────────────────────────────────────────────────
|
|
2286
|
-
|
|
2287
|
-
|
|
2372
|
+
// A salary bound is meaningless without a currency: `minSalary` is a raw
|
|
2373
|
+
// integer and 50000 SEK is not 50000 USD, so an un-currencied range matches
|
|
2374
|
+
// across currencies and quietly means nothing. Requiring exactly one currency
|
|
2375
|
+
// alongside a bound is what makes the comparison sound.
|
|
2376
|
+
minSalaryFloor: salaryBound("Min Salary \u2265"),
|
|
2377
|
+
minSalaryCeil: salaryBound("Min Salary \u2264"),
|
|
2288
2378
|
openToWorkOnly: (0, import_yup20.boolean)().optional().default(false).label("Open to Work Only"),
|
|
2289
2379
|
remoteExperienceOnly: (0, import_yup20.boolean)().optional().default(false).label("Has Remote Experience"),
|
|
2290
2380
|
hasResumeOnly: (0, import_yup20.boolean)().optional().default(false).label("Has Resume"),
|
|
2291
2381
|
onboardingCompletedOnly: (0, import_yup20.boolean)().optional().default(false).label("Onboarding Completed"),
|
|
2292
2382
|
emailVerifiedOnly: (0, import_yup20.boolean)().optional().default(false).label("Email Verified Only"),
|
|
2293
|
-
mobileVerifiedOnly: (0, import_yup20.boolean)().optional().default(false).label("Mobile Verified Only")
|
|
2294
|
-
|
|
2383
|
+
mobileVerifiedOnly: (0, import_yup20.boolean)().optional().default(false).label("Mobile Verified Only"),
|
|
2384
|
+
// ── Lifecycle windows ───────────────────────────────────────────────────────
|
|
2385
|
+
// Relative, because a saved list is resolved fresh on every send: a stored
|
|
2386
|
+
// absolute epoch would freeze at authoring time and the audience would decay
|
|
2387
|
+
// to nothing. The absolute forms below exist for one-shot sends; when both are
|
|
2388
|
+
// present the relative form wins.
|
|
2389
|
+
lastSeenBeforeDays: days("Inactive For (days) \u2265"),
|
|
2390
|
+
lastSeenAfterDays: days("Active Within (days)"),
|
|
2391
|
+
createdAtWithinDays: days("Signed Up Within (days)"),
|
|
2392
|
+
createdAtOlderThanDays: days("Signed Up Before (days ago)"),
|
|
2393
|
+
completenessMin: percent("Profile Completeness \u2265"),
|
|
2394
|
+
completenessMax: percent("Profile Completeness \u2264"),
|
|
2395
|
+
// Absolute epoch-ms forms. Prefer the relative fields above for anything that
|
|
2396
|
+
// repeats.
|
|
2397
|
+
lastSeenBefore: epoch("Last Seen Before"),
|
|
2398
|
+
lastSeenAfter: epoch("Last Seen After"),
|
|
2399
|
+
createdAtFrom: epoch("Signed Up After"),
|
|
2400
|
+
createdAtBefore: epoch("Signed Up Before"),
|
|
2401
|
+
// ── Geography ───────────────────────────────────────────────────────────────
|
|
2402
|
+
// `countries` above matches location.countryCode. These narrow within it.
|
|
2403
|
+
// NOTE: stateCodes are stored bare (`KA`, not `IN-KA`) — see the pending
|
|
2404
|
+
// @todo STATE-CODE-ISO-PREFIX migration. Match the stored form, do not prefix.
|
|
2405
|
+
states: tokens("States"),
|
|
2406
|
+
stateCodes: tokens("State Codes"),
|
|
2407
|
+
cities: tokens("Cities"),
|
|
2408
|
+
// Radius search around a point, against location.geo. All three required
|
|
2409
|
+
// together; validated as a group rather than field-by-field so a half-filled
|
|
2410
|
+
// radius cannot silently match everyone.
|
|
2411
|
+
withinRadius: (0, import_yup20.object)({
|
|
2412
|
+
lat: (0, import_yup20.number)().min(-90).max(90).required().label("Latitude"),
|
|
2413
|
+
lng: (0, import_yup20.number)().min(-180).max(180).required().label("Longitude"),
|
|
2414
|
+
km: (0, import_yup20.number)().moreThan(0).max(2e4).required().label("Radius (km)")
|
|
2415
|
+
}).optional().default(void 0).label("Within Radius"),
|
|
2416
|
+
// ── Signup context (immutable market / locale) ──────────────────────────────
|
|
2417
|
+
signupCountries: tokens("Signup Countries"),
|
|
2418
|
+
signupLocales: tokens("Signup Locales"),
|
|
2419
|
+
signupSources: enumFacet(SupportedSignupContextSources, "Signup Source"),
|
|
2420
|
+
// ── Attainment ──────────────────────────────────────────────────────────────
|
|
2421
|
+
// Absent on users who predate the field. Absent means "unknown", never a
|
|
2422
|
+
// match — filtering on this necessarily excludes them.
|
|
2423
|
+
educationLevels: enumFacet(SupportedEducationLevels, "Education Level"),
|
|
2424
|
+
// ── Consent metadata ────────────────────────────────────────────────────────
|
|
2425
|
+
// `marketingConsent.subscribed` is NOT a facet: it is a hard opt-in gate
|
|
2426
|
+
// applied unconditionally by user-service and is not relaxable from here.
|
|
2427
|
+
jobAlertFrequencies: enumFacet(
|
|
2428
|
+
SupportedJobAlertFrequencies,
|
|
2429
|
+
"Job Alert Frequency"
|
|
2430
|
+
),
|
|
2431
|
+
consentSources: tokens("Consent Source"),
|
|
2432
|
+
consentUpdatedWithinDays: days("Consent Updated Within (days)"),
|
|
2433
|
+
// ── Notification preferences ────────────────────────────────────────────────
|
|
2434
|
+
// Read as opt-IN (`=== true`), matching marketingConsent and NOT the `!== false`
|
|
2435
|
+
// reading used for transactional notification routing. A campaign is bulk mail,
|
|
2436
|
+
// so a channel the user never affirmatively enabled does not count as consent.
|
|
2437
|
+
// `productUpdates` defaults to false on the user document, so targeting it
|
|
2438
|
+
// selects only people who deliberately turned it on.
|
|
2439
|
+
notificationChannelsEnabled: enumFacet(
|
|
2440
|
+
SupportedNotificationChannels,
|
|
2441
|
+
"Notification Channels Enabled"
|
|
2442
|
+
),
|
|
2443
|
+
// ── Tenure / recency ────────────────────────────────────────────────────────
|
|
2444
|
+
// Against workExperiences[].duration and educations[].duration ("YYYY-MM").
|
|
2445
|
+
currentlyEmployed: (0, import_yup20.boolean)().optional().default(false).label("Currently Employed"),
|
|
2446
|
+
graduatedWithinMonths: months("Graduated Within (months)"),
|
|
2447
|
+
skillUsedWithinMonths: months("Skill Used Within (months)"),
|
|
2448
|
+
// ── Profile richness ────────────────────────────────────────────────────────
|
|
2449
|
+
hasImage: (0, import_yup20.boolean)().optional().default(false).label("Has Photo"),
|
|
2450
|
+
hasHeadline: (0, import_yup20.boolean)().optional().default(false).label("Has Headline"),
|
|
2451
|
+
hasAboutMe: (0, import_yup20.boolean)().optional().default(false).label("Has About Me"),
|
|
2452
|
+
hasMobile: (0, import_yup20.boolean)().optional().default(false).label("Has Mobile Number"),
|
|
2453
|
+
// Segment by WHICH profile section is missing (profileCompleteness.missing[]),
|
|
2454
|
+
// rather than only by the overall percentage.
|
|
2455
|
+
missingSections: tokens("Missing Profile Sections"),
|
|
2456
|
+
// ── Employer / institute taxonomy ───────────────────────────────────────────
|
|
2457
|
+
companyTypes: enumFacet(SupportedPageTypes, "Company Type"),
|
|
2458
|
+
instituteTypes: enumFacet(SupportedPageTypes, "Institute Type"),
|
|
2459
|
+
// ── Activity ────────────────────────────────────────────────────────────────
|
|
2460
|
+
// Backed by denormalised counters on the user document, because the source
|
|
2461
|
+
// data lives in other services' collections. Tri-state: `false` is a real
|
|
2462
|
+
// filter ("has NO job alert"), which is the audience an alert nudge wants, so
|
|
2463
|
+
// these are `undefined` when unset rather than defaulting to false.
|
|
2464
|
+
hasJobAlert: (0, import_yup20.boolean)().optional().default(void 0).label("Has a Job Alert"),
|
|
2465
|
+
hasApplied: (0, import_yup20.boolean)().optional().default(void 0).label("Has Applied"),
|
|
2466
|
+
appliedWithinDays: days("Applied Within (days)"),
|
|
2467
|
+
// ── Free-text search (name / email) ─────────────────────────────────────────
|
|
2468
|
+
search: (0, import_yup20.string)().trim().optional().label("Search")
|
|
2469
|
+
}).label("Audience Filter Branch");
|
|
2470
|
+
var rejectUnknownKeys = (schema, allowed) => schema.test(
|
|
2471
|
+
"no-unknown-filter-keys",
|
|
2472
|
+
({ value, label }) => {
|
|
2473
|
+
const known = new Set(allowed());
|
|
2474
|
+
const bad = Object.keys(value ?? {}).filter(
|
|
2475
|
+
(k) => !known.has(k)
|
|
2476
|
+
);
|
|
2477
|
+
return `${label} has unsupported keys: ${bad.join(", ")}`;
|
|
2478
|
+
},
|
|
2479
|
+
(value) => {
|
|
2480
|
+
if (value == null) return true;
|
|
2481
|
+
const known = new Set(allowed());
|
|
2482
|
+
return Object.keys(value).every(
|
|
2483
|
+
(k) => known.has(k)
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
);
|
|
2487
|
+
var BRANCH_KEYS = Object.keys(ListFilterBranchSchema.fields);
|
|
2488
|
+
var ListFilterBranch = rejectUnknownKeys(
|
|
2489
|
+
ListFilterBranchSchema,
|
|
2490
|
+
() => BRANCH_KEYS
|
|
2491
|
+
);
|
|
2492
|
+
var ListFilterSchema = rejectUnknownKeys(
|
|
2493
|
+
ListFilterBranchSchema.shape({
|
|
2494
|
+
or: (0, import_yup20.array)().of(ListFilterBranch).optional().default([]).label("Or Any Of")
|
|
2495
|
+
}).label("Audience Filter"),
|
|
2496
|
+
() => [...BRANCH_KEYS, "or"]
|
|
2497
|
+
);
|
|
2295
2498
|
var MailingListSchema = (0, import_yup20.object)({
|
|
2296
2499
|
name: (0, import_yup20.string)().required().min(1).max(120).label("Name"),
|
|
2297
2500
|
description: (0, import_yup20.string)().optional().max(500).label("Description"),
|
|
2298
2501
|
filter: ListFilterSchema.default(() => ListFilterSchema.getDefault())
|
|
2299
2502
|
}).label("Mailing List");
|
|
2503
|
+
var CampaignTriggerSchema = (0, import_yup20.object)({
|
|
2504
|
+
kind: (0, import_yup20.string)().oneOf(SupportedCampaignTriggerKinds).required().default("manual" /* Manual */).label("Trigger"),
|
|
2505
|
+
// IANA zone, e.g. "Asia/Kolkata". Local wall-clock times are interpreted in it,
|
|
2506
|
+
// so occurrences land at the intended hour across DST rather than drifting.
|
|
2507
|
+
timezone: (0, import_yup20.string)().optional().default("UTC").label("Timezone").when("kind", {
|
|
2508
|
+
is: (k) => k !== "manual" /* Manual */,
|
|
2509
|
+
then: (s) => s.required()
|
|
2510
|
+
}),
|
|
2511
|
+
// kind: once
|
|
2512
|
+
runAt: epoch("Send At").when("kind", {
|
|
2513
|
+
is: "once" /* Once */,
|
|
2514
|
+
then: (s) => s.required()
|
|
2515
|
+
}),
|
|
2516
|
+
// kind: schedule
|
|
2517
|
+
startAt: epoch("Starts").when("kind", {
|
|
2518
|
+
is: "schedule" /* Schedule */,
|
|
2519
|
+
then: (s) => s.required()
|
|
2520
|
+
}),
|
|
2521
|
+
interval: (0, import_yup20.object)({
|
|
2522
|
+
every: (0, import_yup20.number)().integer().min(1).max(365).required().label("Every"),
|
|
2523
|
+
unit: (0, import_yup20.string)().oneOf(SupportedCampaignIntervalUnits).required().label("Unit")
|
|
2524
|
+
}).optional().default(void 0).label("Repeats").when("kind", {
|
|
2525
|
+
is: "schedule" /* Schedule */,
|
|
2526
|
+
then: (s) => s.required()
|
|
2527
|
+
}),
|
|
2528
|
+
/** Local wall-clock send time, "HH:mm" in `timezone`. */
|
|
2529
|
+
timeOfDay: (0, import_yup20.string)().matches(/^([01]\d|2[0-3]):[0-5]\d$/, "Use HH:mm").optional().default("09:00").label("Time of Day"),
|
|
2530
|
+
/** For weekly intervals: ISO weekdays, 1 = Monday .. 7 = Sunday. */
|
|
2531
|
+
byWeekday: (0, import_yup20.array)().of((0, import_yup20.number)().integer().min(1).max(7).required()).optional().default([]).label("On Days"),
|
|
2532
|
+
/**
|
|
2533
|
+
* For monthly intervals: 1-31, or -1 meaning "last day of the month". A month
|
|
2534
|
+
* shorter than the chosen day is clamped to its last day, so 31 in February
|
|
2535
|
+
* fires on the 28th/29th rather than being skipped.
|
|
2536
|
+
*/
|
|
2537
|
+
byMonthday: (0, import_yup20.number)().integer().min(-1).max(31).notOneOf([0]).optional().default(void 0).label("Day of Month"),
|
|
2538
|
+
end: (0, import_yup20.object)({
|
|
2539
|
+
kind: (0, import_yup20.string)().oneOf(SupportedCampaignEndKinds).required().label("Ends"),
|
|
2540
|
+
occurrences: (0, import_yup20.number)().integer().min(1).max(1e4).optional().default(void 0).label("After N Sends").when("kind", {
|
|
2541
|
+
is: "afterOccurrences" /* AfterOccurrences */,
|
|
2542
|
+
then: (s) => s.required()
|
|
2543
|
+
}),
|
|
2544
|
+
date: epoch("End Date").when("kind", {
|
|
2545
|
+
is: "onDate" /* OnDate */,
|
|
2546
|
+
then: (s) => s.required()
|
|
2547
|
+
})
|
|
2548
|
+
}).optional().default(() => ({ kind: "never" /* Never */ })).label("End Condition"),
|
|
2549
|
+
// kind: lifecycle
|
|
2550
|
+
anchorField: (0, import_yup20.string)().oneOf(SupportedCampaignAnchorFields).optional().default(void 0).label("Anchor Date").when("kind", {
|
|
2551
|
+
is: "lifecycle" /* Lifecycle */,
|
|
2552
|
+
then: (s) => s.required()
|
|
2553
|
+
}),
|
|
2554
|
+
/** Days after the anchor at which this user becomes eligible. */
|
|
2555
|
+
offsetDays: (0, import_yup20.number)().integer().min(0).max(3650).optional().default(void 0).label("Days After Anchor").when("kind", {
|
|
2556
|
+
is: "lifecycle" /* Lifecycle */,
|
|
2557
|
+
then: (s) => s.required()
|
|
2558
|
+
}),
|
|
2559
|
+
/**
|
|
2560
|
+
* How long a user stays eligible after crossing the offset. Needed because the
|
|
2561
|
+
* scheduler samples periodically and a zero-width window would miss anyone not
|
|
2562
|
+
* observed on the exact day. A user inside the window on several ticks is still
|
|
2563
|
+
* mailed once: the permanent idempotency key, not this value, guarantees that.
|
|
2564
|
+
*/
|
|
2565
|
+
windowDays: (0, import_yup20.number)().integer().min(1).max(90).optional().default(2).label("Eligibility Window (days)")
|
|
2566
|
+
}).label("Trigger");
|
|
2567
|
+
var BindingSchema = (0, import_yup20.object)({
|
|
2568
|
+
kind: (0, import_yup20.string)().oneOf(SupportedBindingKinds).required().label("Source"),
|
|
2569
|
+
value: (0, import_yup20.string)().required().min(1).max(2e3).label("Value")
|
|
2570
|
+
}).label("Binding");
|
|
2300
2571
|
var CampaignSchema = (0, import_yup20.object)({
|
|
2301
2572
|
name: (0, import_yup20.string)().required().min(1).max(200).label("Campaign Name"),
|
|
2302
|
-
subject
|
|
2303
|
-
// HTML
|
|
2304
|
-
|
|
2573
|
+
// The template renders its own subject; an override is only needed for the
|
|
2574
|
+
// inline-HTML path, hence not required when a template is chosen.
|
|
2575
|
+
subject: (0, import_yup20.string)().optional().max(255).label("Subject").when("templateKey", {
|
|
2576
|
+
is: (v) => !v,
|
|
2577
|
+
then: (s) => s.required().min(1)
|
|
2578
|
+
}),
|
|
2579
|
+
/**
|
|
2580
|
+
* Inline HTML body; may contain {{unsubscribe_url}}. Optional now that a
|
|
2581
|
+
* campaign can instead name a registry template — exactly one of the two is
|
|
2582
|
+
* required. Scheduled and lifecycle campaigns must use a template: the
|
|
2583
|
+
* registry path is the one with a compliance-checked footer and a render test,
|
|
2584
|
+
* and hand-pasted HTML that repeats forever is a mistake that repeats forever.
|
|
2585
|
+
*/
|
|
2586
|
+
html: (0, import_yup20.string)().optional().label("HTML Body").when("templateKey", {
|
|
2587
|
+
is: (v) => !v,
|
|
2588
|
+
then: (s) => s.required().min(1)
|
|
2589
|
+
}),
|
|
2305
2590
|
text: (0, import_yup20.string)().optional().label("Plain-text Body"),
|
|
2306
|
-
listId: (0, import_yup20.string)().required().label("Mailing List")
|
|
2591
|
+
listId: (0, import_yup20.string)().required().label("Mailing List"),
|
|
2592
|
+
/** Key from email-service's template registry. Mutually exclusive with `html`. */
|
|
2593
|
+
templateKey: (0, import_yup20.string)().optional().label("Email Template"),
|
|
2594
|
+
/**
|
|
2595
|
+
* One entry per variable the chosen template declares. Validated at save time
|
|
2596
|
+
* against the template's own `variables`, so a missing required binding is a
|
|
2597
|
+
* rejected save rather than a broken send.
|
|
2598
|
+
*/
|
|
2599
|
+
/**
|
|
2600
|
+
* A DICTIONARY keyed by template variable name, which Yup has no native type
|
|
2601
|
+
* for. It must be built with `lazy` from the keys actually present: a bare
|
|
2602
|
+
* `object()` has no known keys, so under `stripUnknown: true` (which the
|
|
2603
|
+
* consumers' validate helper passes) every binding is "unknown" and the whole
|
|
2604
|
+
* map arrives as `{}` — silently turning a fully-configured campaign into one
|
|
2605
|
+
* whose required variables all appear unbound. Shaping each entry also means a
|
|
2606
|
+
* malformed binding is rejected here rather than at send time.
|
|
2607
|
+
*/
|
|
2608
|
+
bindings: (0, import_yup20.lazy)((value) => {
|
|
2609
|
+
const keys = value && typeof value === "object" ? Object.keys(value) : [];
|
|
2610
|
+
return (0, import_yup20.object)(
|
|
2611
|
+
Object.fromEntries(keys.map((k) => [k, BindingSchema.required()]))
|
|
2612
|
+
).optional().default(() => ({})).label("Template Data");
|
|
2613
|
+
}),
|
|
2614
|
+
/** Registered data source supplying per-recipient content, plus its params. */
|
|
2615
|
+
dataSource: (0, import_yup20.object)({
|
|
2616
|
+
id: (0, import_yup20.string)().required().label("Data Source"),
|
|
2617
|
+
/**
|
|
2618
|
+
* A free-form bag whose keys are defined by the chosen source's OWN
|
|
2619
|
+
* `paramsSchema`, which this package cannot see. Same `lazy` treatment as
|
|
2620
|
+
* `bindings` above, and for the same reason: a bare `object()` has no known
|
|
2621
|
+
* keys, so under `stripUnknown: true` every param is "unknown" and the bag
|
|
2622
|
+
* arrives as `{}` — a campaign configured to mail remote Python roles in
|
|
2623
|
+
* Bangalore silently saves as an unfiltered platform-wide digest.
|
|
2624
|
+
*
|
|
2625
|
+
* Values stay unvalidated here on purpose; the marketing service validates
|
|
2626
|
+
* them against the real source schema in `checkCampaignConfig`, which is the
|
|
2627
|
+
* only place that knows what a given source accepts.
|
|
2628
|
+
*/
|
|
2629
|
+
params: (0, import_yup20.lazy)((value) => {
|
|
2630
|
+
const keys = value && typeof value === "object" ? Object.keys(value) : [];
|
|
2631
|
+
return (0, import_yup20.object)(
|
|
2632
|
+
Object.fromEntries(keys.map((k) => [k, (0, import_yup20.mixed)().optional()]))
|
|
2633
|
+
).optional().default(() => ({})).label("Parameters");
|
|
2634
|
+
})
|
|
2635
|
+
}).optional().default(void 0).label("Data Source"),
|
|
2636
|
+
trigger: CampaignTriggerSchema.default(
|
|
2637
|
+
() => CampaignTriggerSchema.getDefault()
|
|
2638
|
+
),
|
|
2639
|
+
// ── Safety ──────────────────────────────────────────────────────────────────
|
|
2640
|
+
// Both default to the safe position: a new campaign neither runs nor sends
|
|
2641
|
+
// until someone deliberately says so, and says so twice.
|
|
2642
|
+
enabled: (0, import_yup20.boolean)().optional().default(false).label("Enabled"),
|
|
2643
|
+
dryRun: (0, import_yup20.boolean)().optional().default(true).label("Dry Run"),
|
|
2644
|
+
maxRecipients: (0, import_yup20.number)().integer().min(1).max(CAMPAIGN_MAX_RECIPIENTS_CEILING).optional().default(void 0).label("Max Recipients"),
|
|
2645
|
+
utm: (0, import_yup20.object)({
|
|
2646
|
+
campaign: (0, import_yup20.string)().optional().max(120).label("utm_campaign"),
|
|
2647
|
+
content: (0, import_yup20.string)().optional().max(120).label("utm_content")
|
|
2648
|
+
}).optional().default(void 0).label("Link Tagging")
|
|
2307
2649
|
}).label("Campaign");
|
|
2308
2650
|
|
|
2309
|
-
// src/marketing/marketing.constant.ts
|
|
2310
|
-
var CampaignStatus = /* @__PURE__ */ ((CampaignStatus2) => {
|
|
2311
|
-
CampaignStatus2["Draft"] = "draft";
|
|
2312
|
-
CampaignStatus2["Sending"] = "sending";
|
|
2313
|
-
CampaignStatus2["Sent"] = "sent";
|
|
2314
|
-
CampaignStatus2["Failed"] = "failed";
|
|
2315
|
-
return CampaignStatus2;
|
|
2316
|
-
})(CampaignStatus || {});
|
|
2317
|
-
var SupportedCampaignStatuses = Object.values(CampaignStatus);
|
|
2318
|
-
|
|
2319
2651
|
// src/pagination/pagination.schema.ts
|
|
2320
2652
|
var import_yup21 = require("yup");
|
|
2321
2653
|
var PaginationSchema = (0, import_yup21.object)().shape({
|
|
@@ -2563,6 +2895,13 @@ var UserGeneralDetailSchema = (0, import_yup33.object)({
|
|
|
2563
2895
|
}).required().label("Name"),
|
|
2564
2896
|
headline: (0, import_yup33.string)().trim().max(200).optional().label("Headline"),
|
|
2565
2897
|
image: (0, import_yup33.string)().optional().label("Image"),
|
|
2898
|
+
// The DISPLAYED avatar (`image`) is a flattened export with any status frame
|
|
2899
|
+
// ("#OpenToWork", …) already burned into the pixels. `imageOriginal` keeps the
|
|
2900
|
+
// un-framed source the user uploaded, so the image builder can reopen it and
|
|
2901
|
+
// apply a DIFFERENT frame instead of stacking a second one on top of the first.
|
|
2902
|
+
// Optional and absent on every pre-existing user: treat a missing value as
|
|
2903
|
+
// "only the flattened image is available".
|
|
2904
|
+
imageOriginal: (0, import_yup33.string)().optional().label("Original Image"),
|
|
2566
2905
|
aboutMe: (0, import_yup33.string)().trim().max(3e3).optional().label("About Me"),
|
|
2567
2906
|
email: (0, import_yup33.string)().required().label("Email"),
|
|
2568
2907
|
mobile: (0, import_yup33.string)().nullable().optional().label("Mobile"),
|
|
@@ -2890,8 +3229,16 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
|
|
|
2890
3229
|
0 && (module.exports = {
|
|
2891
3230
|
AnswerChoiceType,
|
|
2892
3231
|
ApplicationReceivePreference,
|
|
3232
|
+
BindingKind,
|
|
3233
|
+
BindingSchema,
|
|
3234
|
+
CAMPAIGN_MAX_RECIPIENTS_CEILING,
|
|
3235
|
+
CampaignAnchorField,
|
|
3236
|
+
CampaignEndKind,
|
|
3237
|
+
CampaignIntervalUnit,
|
|
2893
3238
|
CampaignSchema,
|
|
2894
3239
|
CampaignStatus,
|
|
3240
|
+
CampaignTriggerKind,
|
|
3241
|
+
CampaignTriggerSchema,
|
|
2895
3242
|
ChoiceQuestionSchema,
|
|
2896
3243
|
Common,
|
|
2897
3244
|
CompensationType,
|
|
@@ -2933,12 +3280,15 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
|
|
|
2933
3280
|
JobSearchUrgency,
|
|
2934
3281
|
JobStatus,
|
|
2935
3282
|
JobSubCategory,
|
|
3283
|
+
ListFilterBranch,
|
|
3284
|
+
ListFilterBranchSchema,
|
|
2936
3285
|
ListFilterSchema,
|
|
2937
3286
|
LocationSchema,
|
|
2938
3287
|
MIN_SALARY_LOWER_BOUND,
|
|
2939
3288
|
MIN_SALARY_UPPER_BOUND,
|
|
2940
3289
|
MailingListSchema,
|
|
2941
3290
|
NotificationChannel,
|
|
3291
|
+
OccurrenceStatus,
|
|
2942
3292
|
PREFILL_MIRRORED_KEYS,
|
|
2943
3293
|
PRIVATE_PROFILE_FIELDS,
|
|
2944
3294
|
PageSchema,
|
|
@@ -2968,7 +3318,12 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
|
|
|
2968
3318
|
StudyType,
|
|
2969
3319
|
SupportedAnswerChoiceTypes,
|
|
2970
3320
|
SupportedApplicationReceivePreferences,
|
|
3321
|
+
SupportedBindingKinds,
|
|
3322
|
+
SupportedCampaignAnchorFields,
|
|
3323
|
+
SupportedCampaignEndKinds,
|
|
3324
|
+
SupportedCampaignIntervalUnits,
|
|
2971
3325
|
SupportedCampaignStatuses,
|
|
3326
|
+
SupportedCampaignTriggerKinds,
|
|
2972
3327
|
SupportedCompensationTypes,
|
|
2973
3328
|
SupportedContactTypes,
|
|
2974
3329
|
SupportedEducationLevels,
|
|
@@ -2984,6 +3339,7 @@ var StudentCompletenessSchema = UserCompletenessSchema.omit([
|
|
|
2984
3339
|
SupportedJobStatuses,
|
|
2985
3340
|
SupportedJobSubCategories,
|
|
2986
3341
|
SupportedNotificationChannels,
|
|
3342
|
+
SupportedOccurrenceStatuses,
|
|
2987
3343
|
SupportedPageStatuses,
|
|
2988
3344
|
SupportedPageTypes,
|
|
2989
3345
|
SupportedPostStatuses,
|