@thejob/schema 2.1.3 → 2.1.4
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/.claude/settings.local.json +5 -5
- package/CLAUDE.md +62 -0
- package/dist/index.cjs +204 -137
- package/dist/index.d.cts +175 -123
- package/dist/index.d.ts +175 -123
- package/dist/index.js +170 -106
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/saved-search/saved-search.constant.ts +17 -0
- package/src/saved-search/saved-search.schema.ts +84 -0
package/dist/index.js
CHANGED
|
@@ -2127,8 +2127,69 @@ var DefaultPaginationOptions = {
|
|
|
2127
2127
|
limit: 10
|
|
2128
2128
|
};
|
|
2129
2129
|
|
|
2130
|
+
// src/saved-search/saved-search.schema.ts
|
|
2131
|
+
import { number as number9, object as object20, string as string17 } from "yup";
|
|
2132
|
+
|
|
2133
|
+
// src/saved-search/saved-search.constant.ts
|
|
2134
|
+
var SavedSearchStatus = /* @__PURE__ */ ((SavedSearchStatus2) => {
|
|
2135
|
+
SavedSearchStatus2["Active"] = "active";
|
|
2136
|
+
SavedSearchStatus2["Paused"] = "paused";
|
|
2137
|
+
return SavedSearchStatus2;
|
|
2138
|
+
})(SavedSearchStatus || {});
|
|
2139
|
+
var SupportedSavedSearchStatuses = Object.values(SavedSearchStatus);
|
|
2140
|
+
|
|
2141
|
+
// src/saved-search/saved-search.schema.ts
|
|
2142
|
+
var SavedSearchSchema = object20({
|
|
2143
|
+
/** Public identifier. Server-assigned. */
|
|
2144
|
+
shortId: string17().trim().label("Short Id"),
|
|
2145
|
+
/**
|
|
2146
|
+
* Owner's Better Auth account id. Server-assigned from the verified token,
|
|
2147
|
+
* never accepted from a request body.
|
|
2148
|
+
*/
|
|
2149
|
+
ownerUserId: string17().trim().label("Owner"),
|
|
2150
|
+
/** What the user calls this search, e.g. "Remote React roles". */
|
|
2151
|
+
label: string17().trim().max(120).required().label("Label"),
|
|
2152
|
+
/**
|
|
2153
|
+
* The free-text query. Optional because a search can be filters-only ("all
|
|
2154
|
+
* remote jobs in Sweden"), which is a legitimate alert to want.
|
|
2155
|
+
*/
|
|
2156
|
+
search: string17().trim().max(500).optional().label("Search Text"),
|
|
2157
|
+
/**
|
|
2158
|
+
* Field filters, in the same `{ field: values }` shape the search endpoint
|
|
2159
|
+
* parses from `filter[field]=a,b`. Held as a free-form object rather than a
|
|
2160
|
+
* closed schema because the job facets it addresses live in jobs-service and
|
|
2161
|
+
* change independently; validating them here would put this package in the
|
|
2162
|
+
* business of tracking that vocabulary.
|
|
2163
|
+
*/
|
|
2164
|
+
filter: object20().optional().default({}).label("Filters"),
|
|
2165
|
+
/**
|
|
2166
|
+
* How often to mail matches. `off` keeps the search saved but silent, which is
|
|
2167
|
+
* the difference between "I don't want mail" and "delete my search".
|
|
2168
|
+
*
|
|
2169
|
+
* Reuses the cadence enum from `marketingConsent.jobAlertFrequency` rather
|
|
2170
|
+
* than defining a second one: two vocabularies for the same concept is how
|
|
2171
|
+
* they drift.
|
|
2172
|
+
*/
|
|
2173
|
+
frequency: string17().oneOf(SupportedJobAlertFrequencies).default("off" /* Off */).label("Alert Frequency"),
|
|
2174
|
+
status: string17().oneOf(SupportedSavedSearchStatuses).default("active" /* Active */).label("Status"),
|
|
2175
|
+
/**
|
|
2176
|
+
* Epoch ms of the last alert sent for this search. The matching sweep reads it
|
|
2177
|
+
* to decide whether the cadence is due, and writes it only AFTER a successful
|
|
2178
|
+
* publish, so a failed run retries next tick rather than silently skipping a
|
|
2179
|
+
* period.
|
|
2180
|
+
*/
|
|
2181
|
+
lastNotifiedAt: number9().optional().label("Last Notified At"),
|
|
2182
|
+
/**
|
|
2183
|
+
* Epoch ms high-water mark: only jobs created after this are "new" for the
|
|
2184
|
+
* next alert. Kept separate from `lastNotifiedAt` because a run that finds no
|
|
2185
|
+
* matches should still advance what counts as new, without implying mail was
|
|
2186
|
+
* sent.
|
|
2187
|
+
*/
|
|
2188
|
+
lastMatchedAt: number9().optional().label("Last Matched At")
|
|
2189
|
+
}).noUnknown().strict();
|
|
2190
|
+
|
|
2130
2191
|
// src/report/report.schema.ts
|
|
2131
|
-
import { mixed as mixed4, object as
|
|
2192
|
+
import { mixed as mixed4, object as object21, string as string18 } from "yup";
|
|
2132
2193
|
|
|
2133
2194
|
// src/report/report.constant.ts
|
|
2134
2195
|
var ResourceType = /* @__PURE__ */ ((ResourceType3) => {
|
|
@@ -2152,15 +2213,15 @@ var ReportReason = /* @__PURE__ */ ((ReportReason3) => {
|
|
|
2152
2213
|
var SupportedReportReasons = Object.values(ReportReason);
|
|
2153
2214
|
|
|
2154
2215
|
// src/report/report.schema.ts
|
|
2155
|
-
var ReportSchema =
|
|
2216
|
+
var ReportSchema = object21({
|
|
2156
2217
|
type: mixed4().oneOf(SupportedResourceTypes).required().label("Type"),
|
|
2157
|
-
resourceId:
|
|
2218
|
+
resourceId: string18().required().label("Resource ID"),
|
|
2158
2219
|
reason: mixed4().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
|
|
2159
|
-
comment:
|
|
2220
|
+
comment: string18().max(1e3).optional().label("Comment")
|
|
2160
2221
|
}).label("Report Schema");
|
|
2161
2222
|
|
|
2162
2223
|
// src/feedback/feedback.schema.ts
|
|
2163
|
-
import { mixed as mixed5, object as
|
|
2224
|
+
import { mixed as mixed5, object as object22, string as string19 } from "yup";
|
|
2164
2225
|
|
|
2165
2226
|
// src/feedback/feedback.constant.ts
|
|
2166
2227
|
var FeedbackType = /* @__PURE__ */ ((FeedbackType3) => {
|
|
@@ -2183,63 +2244,63 @@ var SupportedFeedbackStatuses = Object.values(FeedbackStatus);
|
|
|
2183
2244
|
var FEEDBACK_MESSAGE_MAX = 4e3;
|
|
2184
2245
|
|
|
2185
2246
|
// src/feedback/feedback.schema.ts
|
|
2186
|
-
var FeedbackSchema =
|
|
2247
|
+
var FeedbackSchema = object22({
|
|
2187
2248
|
type: mixed5().oneOf(SupportedFeedbackTypes).required().label("Type"),
|
|
2188
|
-
message:
|
|
2189
|
-
email:
|
|
2190
|
-
pageUrl:
|
|
2249
|
+
message: string19().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
|
|
2250
|
+
email: string19().email().optional().label("Email"),
|
|
2251
|
+
pageUrl: string19().optional().label("Page URL"),
|
|
2191
2252
|
status: mixed5().oneOf(SupportedFeedbackStatuses).optional().label("Status")
|
|
2192
2253
|
}).label("Feedback Schema");
|
|
2193
2254
|
|
|
2194
2255
|
// src/user/user.schema.ts
|
|
2195
|
-
import { array as array14, boolean as boolean15, number as
|
|
2256
|
+
import { array as array14, boolean as boolean15, number as number11, object as object34, string as string32 } from "yup";
|
|
2196
2257
|
|
|
2197
2258
|
// src/user/work-experience.schema.ts
|
|
2198
|
-
import { boolean as boolean12, object as
|
|
2199
|
-
var WorkExperienceSchema =
|
|
2259
|
+
import { boolean as boolean12, object as object23, string as string20 } from "yup";
|
|
2260
|
+
var WorkExperienceSchema = object23().shape({
|
|
2200
2261
|
company: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company"),
|
|
2201
|
-
designation:
|
|
2262
|
+
designation: string20().trim().required().label("Designation"),
|
|
2202
2263
|
duration: DurationSchema({
|
|
2203
2264
|
format: "YYYY-MM",
|
|
2204
2265
|
startLabel: "Start Date",
|
|
2205
2266
|
endLabel: "End Date"
|
|
2206
2267
|
}).required().label("Duration"),
|
|
2207
|
-
description:
|
|
2268
|
+
description: string20().trim().max(3e3).optional().label("Description"),
|
|
2208
2269
|
location: LocationSchema.required().label("Location"),
|
|
2209
2270
|
isRemote: boolean12().oneOf([true, false]).default(false).label("Is Remote")
|
|
2210
2271
|
}).noUnknown().strict().label("Work Experience");
|
|
2211
2272
|
|
|
2212
2273
|
// src/user/education.schema.ts
|
|
2213
|
-
import { boolean as boolean13, object as
|
|
2214
|
-
var EducationSchema =
|
|
2274
|
+
import { boolean as boolean13, object as object24, string as string21 } from "yup";
|
|
2275
|
+
var EducationSchema = object24({
|
|
2215
2276
|
institute: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute"),
|
|
2216
|
-
course:
|
|
2217
|
-
fieldOfStudy:
|
|
2277
|
+
course: string21().trim().required().label("Course"),
|
|
2278
|
+
fieldOfStudy: string21().trim().required().label("Field of Study"),
|
|
2218
2279
|
duration: DurationSchema({
|
|
2219
2280
|
format: "YYYY-MM",
|
|
2220
2281
|
startLabel: "Start Date",
|
|
2221
2282
|
endLabel: "End Date"
|
|
2222
2283
|
}).required().label("Duration"),
|
|
2223
|
-
description:
|
|
2284
|
+
description: string21().trim().max(3e3).optional().label("Description"),
|
|
2224
2285
|
isDistanceLearning: boolean13().default(false).label("Is Distance Learning"),
|
|
2225
2286
|
location: LocationSchema.required().label("Location"),
|
|
2226
|
-
studyType:
|
|
2287
|
+
studyType: string21().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
|
|
2227
2288
|
}).noUnknown().strict().label("Education");
|
|
2228
2289
|
|
|
2229
2290
|
// src/user/user-skill.schema.ts
|
|
2230
|
-
import { object as
|
|
2231
|
-
var UserSkillSchema =
|
|
2232
|
-
name:
|
|
2233
|
-
proficiencyLevel:
|
|
2291
|
+
import { object as object25, string as string22 } from "yup";
|
|
2292
|
+
var UserSkillSchema = object25({
|
|
2293
|
+
name: string22().required().label("Name"),
|
|
2294
|
+
proficiencyLevel: string22().oneOf(SupportedProficiencyLevels).required().label("Proficiency Level"),
|
|
2234
2295
|
lastUsed: dateString().format("YYYY-MM").nullable().optional().label("Last Used")
|
|
2235
2296
|
}).noUnknown().strict().label("Skill");
|
|
2236
2297
|
|
|
2237
2298
|
// src/user/project.schema.ts
|
|
2238
|
-
import { object as
|
|
2239
|
-
var UserProjectSchema =
|
|
2240
|
-
name:
|
|
2241
|
-
url:
|
|
2242
|
-
description:
|
|
2299
|
+
import { object as object26, string as string23 } from "yup";
|
|
2300
|
+
var UserProjectSchema = object26({
|
|
2301
|
+
name: string23().trim().max(50).required().label("Name"),
|
|
2302
|
+
url: string23().optional().label("URL"),
|
|
2303
|
+
description: string23().trim().min(100).max(3e3).required().label("Description"),
|
|
2243
2304
|
duration: DurationSchema({
|
|
2244
2305
|
format: "YYYY-MM",
|
|
2245
2306
|
startLabel: "Start Date",
|
|
@@ -2248,65 +2309,65 @@ var UserProjectSchema = object25({
|
|
|
2248
2309
|
}).noUnknown().strict().label("Project");
|
|
2249
2310
|
|
|
2250
2311
|
// src/user/user-certification.schema.ts
|
|
2251
|
-
import { object as
|
|
2252
|
-
var UserCertificationSchema =
|
|
2253
|
-
name:
|
|
2312
|
+
import { object as object27, string as string24 } from "yup";
|
|
2313
|
+
var UserCertificationSchema = object27({
|
|
2314
|
+
name: string24().trim().max(100).required().label("Name"),
|
|
2254
2315
|
// TODO: Add validation for authority
|
|
2255
|
-
authority:
|
|
2256
|
-
licenseNumber:
|
|
2316
|
+
authority: string24().trim().max(100).required().label("Authority"),
|
|
2317
|
+
licenseNumber: string24().trim().max(50).optional().label("License Number"),
|
|
2257
2318
|
duration: DurationSchema({
|
|
2258
2319
|
format: "YYYY-MM",
|
|
2259
2320
|
startLabel: "Start Date",
|
|
2260
2321
|
endLabel: "End Date"
|
|
2261
2322
|
}).optional().nullable().label("Duration"),
|
|
2262
|
-
url:
|
|
2323
|
+
url: string24().optional().label("URL")
|
|
2263
2324
|
}).noUnknown().strict().label("Certification");
|
|
2264
2325
|
|
|
2265
2326
|
// src/user/user-interest.schema.ts
|
|
2266
|
-
import { string as
|
|
2267
|
-
var UserInterestSchema =
|
|
2327
|
+
import { string as string25 } from "yup";
|
|
2328
|
+
var UserInterestSchema = string25().trim().required().label("Interest");
|
|
2268
2329
|
|
|
2269
2330
|
// src/user/user-language.schema.ts
|
|
2270
|
-
import { object as
|
|
2271
|
-
var UserLanguageSchema =
|
|
2272
|
-
name:
|
|
2273
|
-
proficiencyLevel:
|
|
2331
|
+
import { object as object28, string as string26 } from "yup";
|
|
2332
|
+
var UserLanguageSchema = object28({
|
|
2333
|
+
name: string26().trim().required().label("Name"),
|
|
2334
|
+
proficiencyLevel: string26().oneOf(SupportedProficiencyLevels).trim().max(50).required().label("Proficiency Level")
|
|
2274
2335
|
}).noUnknown().strict().label("Language");
|
|
2275
2336
|
|
|
2276
2337
|
// src/user/user-additional-info.schema.ts
|
|
2277
|
-
import { object as
|
|
2278
|
-
var UserAdditionalInfoSchema =
|
|
2279
|
-
title:
|
|
2280
|
-
description:
|
|
2338
|
+
import { object as object29, string as string27 } from "yup";
|
|
2339
|
+
var UserAdditionalInfoSchema = object29({
|
|
2340
|
+
title: string27().trim().max(60).required().label("Title"),
|
|
2341
|
+
description: string27().trim().max(1e3).required().label("Description")
|
|
2281
2342
|
}).noUnknown().strict().label("User Additional Info");
|
|
2282
2343
|
|
|
2283
2344
|
// src/user/general-detail.schema.ts
|
|
2284
|
-
import { object as
|
|
2285
|
-
var UserGeneralDetailSchema =
|
|
2286
|
-
id:
|
|
2287
|
-
name:
|
|
2288
|
-
first:
|
|
2289
|
-
last:
|
|
2345
|
+
import { object as object30, string as string28 } from "yup";
|
|
2346
|
+
var UserGeneralDetailSchema = object30({
|
|
2347
|
+
id: string28().optional().label("ID"),
|
|
2348
|
+
name: object30().shape({
|
|
2349
|
+
first: string28().trim().max(50).required().label("First Name"),
|
|
2350
|
+
last: string28().trim().max(50).optional().label("Last Name")
|
|
2290
2351
|
}).required().label("Name"),
|
|
2291
|
-
headline:
|
|
2292
|
-
image:
|
|
2293
|
-
aboutMe:
|
|
2294
|
-
email:
|
|
2295
|
-
mobile:
|
|
2296
|
-
emailVerified:
|
|
2297
|
-
mobileVerified:
|
|
2298
|
-
experienceLevel:
|
|
2352
|
+
headline: string28().trim().max(200).optional().label("Headline"),
|
|
2353
|
+
image: string28().optional().label("Image"),
|
|
2354
|
+
aboutMe: string28().trim().max(3e3).optional().label("About Me"),
|
|
2355
|
+
email: string28().required().label("Email"),
|
|
2356
|
+
mobile: string28().nullable().optional().label("Mobile"),
|
|
2357
|
+
emailVerified: string28().nullable().optional().label("Email Verified"),
|
|
2358
|
+
mobileVerified: string28().nullable().optional().label("Mobile Verified"),
|
|
2359
|
+
experienceLevel: string28().oneOf(SupportedExperienceLevels).required().label("Experience level"),
|
|
2299
2360
|
location: LocationSchema.required().label("Location"),
|
|
2300
|
-
region:
|
|
2301
|
-
country:
|
|
2302
|
-
lang:
|
|
2361
|
+
region: object30().shape({
|
|
2362
|
+
country: string28().trim().required().label("Region Country"),
|
|
2363
|
+
lang: string28().trim().required().label("Region Language")
|
|
2303
2364
|
}).optional().default(void 0).label("Region"),
|
|
2304
|
-
profileVisibility:
|
|
2365
|
+
profileVisibility: string28().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
|
|
2305
2366
|
}).noUnknown().strict().label("General Detail");
|
|
2306
2367
|
|
|
2307
2368
|
// src/user/user-job-preferences.schema.ts
|
|
2308
|
-
import { array as array11, boolean as boolean14, date as date2, number as
|
|
2309
|
-
var UserJobPreferencesSchema =
|
|
2369
|
+
import { array as array11, boolean as boolean14, date as date2, number as number10, object as object31, string as string29 } from "yup";
|
|
2370
|
+
var UserJobPreferencesSchema = object31({
|
|
2310
2371
|
/**
|
|
2311
2372
|
* Whether the job seeker is openly signalling availability. Surfaces the
|
|
2312
2373
|
* "Open to work" badge on the public profile and boosts visibility in
|
|
@@ -2318,7 +2379,7 @@ var UserJobPreferencesSchema = object30({
|
|
|
2318
2379
|
* and can downgrade the visibility of expired or low-relevance postings for
|
|
2319
2380
|
* users in `passively_browsing` mode.
|
|
2320
2381
|
*/
|
|
2321
|
-
jobSearchUrgency:
|
|
2382
|
+
jobSearchUrgency: string29().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
|
|
2322
2383
|
/**
|
|
2323
2384
|
* Locations the user wants to work in. Stores the full `LocationSchema`
|
|
2324
2385
|
* shape (country, city, state, geo, etc.) as returned by the locations
|
|
@@ -2335,7 +2396,7 @@ var UserJobPreferencesSchema = object30({
|
|
|
2335
2396
|
* Both reuse the same `ExperienceLevel` taxonomy from `common.constant.ts`
|
|
2336
2397
|
* so search/match logic doesn't have to translate between two vocabularies.
|
|
2337
2398
|
*/
|
|
2338
|
-
targetExperienceLevels: array11().of(
|
|
2399
|
+
targetExperienceLevels: array11().of(string29().oneOf(SupportedExperienceLevels).required()).min(1, "Pick at least one experience level.").max(2, "You can select at most 2 experience levels.").required().label("Target Experience Levels"),
|
|
2339
2400
|
/**
|
|
2340
2401
|
* Specializations the user is searching for - free-form titles.
|
|
2341
2402
|
*
|
|
@@ -2344,19 +2405,19 @@ var UserJobPreferencesSchema = object30({
|
|
|
2344
2405
|
* surfaces a curated "Popular Roles" picker for discovery, but the user
|
|
2345
2406
|
* can ultimately add any title; matching/normalization happens downstream.
|
|
2346
2407
|
*/
|
|
2347
|
-
jobRoles: array11().of(
|
|
2408
|
+
jobRoles: array11().of(string29().trim().max(120).required()).min(1, "Pick at least one role.").required().label("Job Roles"),
|
|
2348
2409
|
/**
|
|
2349
2410
|
* Minimum acceptable annual base salary, in `minSalaryCurrency`.
|
|
2350
2411
|
* Stored as an integer in the currency's major unit (e.g. dollars, not cents).
|
|
2351
2412
|
* Defaults to 0 ("any salary") rather than being optional - every job-seeker
|
|
2352
2413
|
* has a floor, even if it's zero.
|
|
2353
2414
|
*/
|
|
2354
|
-
minSalary:
|
|
2355
|
-
minSalaryCurrency:
|
|
2415
|
+
minSalary: number10().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
|
|
2416
|
+
minSalaryCurrency: string29().oneOf(SupportedSalaryCurrencies).default("USD").required().label("Minimum Salary Currency"),
|
|
2356
2417
|
/**
|
|
2357
2418
|
* Marketing-attribution capture from the final onboarding step.
|
|
2358
2419
|
*/
|
|
2359
|
-
referralSource:
|
|
2420
|
+
referralSource: string29().oneOf(SupportedReferralSources).required().label("Referral Source"),
|
|
2360
2421
|
/**
|
|
2361
2422
|
* Resumes the user has uploaded. Embedded directly on the user document
|
|
2362
2423
|
* (capped at 5) - they're cheap to denormalize, every wizard step that
|
|
@@ -2371,15 +2432,15 @@ var UserJobPreferencesSchema = object30({
|
|
|
2371
2432
|
* live in their own collection.
|
|
2372
2433
|
*/
|
|
2373
2434
|
resumes: array11().of(
|
|
2374
|
-
|
|
2435
|
+
object31({
|
|
2375
2436
|
// Stable id assigned on upload - used to address a specific entry
|
|
2376
2437
|
// for delete / set-primary operations without exposing the GCS URL
|
|
2377
2438
|
// as a route key.
|
|
2378
|
-
id:
|
|
2379
|
-
url:
|
|
2380
|
-
filename:
|
|
2381
|
-
sizeBytes:
|
|
2382
|
-
mimeType:
|
|
2439
|
+
id: string29().required().label("ID"),
|
|
2440
|
+
url: string29().required().label("Resume URL"),
|
|
2441
|
+
filename: string29().trim().max(255).optional().label("Filename"),
|
|
2442
|
+
sizeBytes: number10().integer().min(0).optional().label("Size (bytes)"),
|
|
2443
|
+
mimeType: string29().trim().max(120).optional().label("MIME Type"),
|
|
2383
2444
|
uploadedAt: date2().required().label("Uploaded At"),
|
|
2384
2445
|
isPrimary: boolean14().default(false).label("Is Primary")
|
|
2385
2446
|
}).noUnknown().strict().label("Resume")
|
|
@@ -2408,12 +2469,12 @@ var UserJobPreferencesSchema = object30({
|
|
|
2408
2469
|
* APIs. If `user.<field>` is empty there, the correct behavior is empty,
|
|
2409
2470
|
* because the user has not confirmed it yet.
|
|
2410
2471
|
*/
|
|
2411
|
-
pendingPrefill:
|
|
2412
|
-
headline:
|
|
2413
|
-
aboutMe:
|
|
2414
|
-
mobile:
|
|
2472
|
+
pendingPrefill: object31({
|
|
2473
|
+
headline: string29().trim().optional().label("Pending Headline"),
|
|
2474
|
+
aboutMe: string29().trim().optional().label("Pending About Me"),
|
|
2475
|
+
mobile: string29().trim().optional().label("Pending Mobile"),
|
|
2415
2476
|
location: LocationSchema.optional().default(void 0).label("Pending Location"),
|
|
2416
|
-
experienceLevel:
|
|
2477
|
+
experienceLevel: string29().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
|
|
2417
2478
|
socialAccounts: array11().of(SocialAccountSchema.required()).optional().label("Pending Social Accounts"),
|
|
2418
2479
|
workExperiences: array11().of(WorkExperienceSchema.required()).optional().label("Pending Work Experiences"),
|
|
2419
2480
|
educations: array11().of(EducationSchema.required()).optional().label("Pending Educations")
|
|
@@ -2421,35 +2482,35 @@ var UserJobPreferencesSchema = object30({
|
|
|
2421
2482
|
}).noUnknown().strict().label("User Job Preferences Schema");
|
|
2422
2483
|
|
|
2423
2484
|
// src/user/user-recruiter-profile.schema.ts
|
|
2424
|
-
import { array as array12, object as
|
|
2425
|
-
var RecruiterPageLinkSchema =
|
|
2485
|
+
import { array as array12, object as object32, string as string30 } from "yup";
|
|
2486
|
+
var RecruiterPageLinkSchema = object32({
|
|
2426
2487
|
page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company Page"),
|
|
2427
|
-
jobTitle:
|
|
2488
|
+
jobTitle: string30().trim().max(100).optional().label("Job Title")
|
|
2428
2489
|
}).noUnknown().strict().label("Recruiter Page Link");
|
|
2429
|
-
var UserRecruiterProfileSchema =
|
|
2430
|
-
recruiterProfile:
|
|
2490
|
+
var UserRecruiterProfileSchema = object32({
|
|
2491
|
+
recruiterProfile: object32({
|
|
2431
2492
|
hiringFor: array12().of(RecruiterPageLinkSchema).default([]).label("Hiring For")
|
|
2432
2493
|
}).optional().default(void 0).label("Recruiter Profile")
|
|
2433
2494
|
}).noUnknown().strict().label("User Recruiter Profile");
|
|
2434
2495
|
|
|
2435
2496
|
// src/user/user-coordinator-profile.schema.ts
|
|
2436
|
-
import { array as array13, object as
|
|
2437
|
-
var CoordinatorPageLinkSchema =
|
|
2497
|
+
import { array as array13, object as object33, string as string31 } from "yup";
|
|
2498
|
+
var CoordinatorPageLinkSchema = object33({
|
|
2438
2499
|
page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute Page"),
|
|
2439
|
-
jobTitle:
|
|
2500
|
+
jobTitle: string31().trim().max(100).optional().label("Job Title")
|
|
2440
2501
|
}).noUnknown().strict().label("Coordinator Page Link");
|
|
2441
|
-
var UserCoordinatorProfileSchema =
|
|
2442
|
-
coordinatorProfile:
|
|
2502
|
+
var UserCoordinatorProfileSchema = object33({
|
|
2503
|
+
coordinatorProfile: object33({
|
|
2443
2504
|
coordinatesAt: array13().of(CoordinatorPageLinkSchema).default([]).label("Coordinates At")
|
|
2444
2505
|
}).optional().default(void 0).label("Coordinator Profile")
|
|
2445
2506
|
}).noUnknown().strict().label("User Coordinator Profile");
|
|
2446
2507
|
|
|
2447
2508
|
// src/user/user.schema.ts
|
|
2448
|
-
var UserSchema =
|
|
2509
|
+
var UserSchema = object34({
|
|
2449
2510
|
/**
|
|
2450
2511
|
* Related auth account id from auth Server (e.g. Better auth https://auth.thejob.dev)
|
|
2451
2512
|
*/
|
|
2452
|
-
authAccountId:
|
|
2513
|
+
authAccountId: string32().required().label("Auth Account ID"),
|
|
2453
2514
|
/**
|
|
2454
2515
|
* Social media information about the user (e.g. LinkedIn, GitHub, etc.)
|
|
2455
2516
|
*/
|
|
@@ -2489,11 +2550,11 @@ var UserSchema = object33({
|
|
|
2489
2550
|
/**
|
|
2490
2551
|
* Status of the user account
|
|
2491
2552
|
*/
|
|
2492
|
-
status:
|
|
2553
|
+
status: string32().oneOf(SupportedUserStatuses).required().label("Status"),
|
|
2493
2554
|
/**
|
|
2494
2555
|
* Roles assigned to the user
|
|
2495
2556
|
*/
|
|
2496
|
-
roles: array14().of(
|
|
2557
|
+
roles: array14().of(string32().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
|
|
2497
2558
|
/**
|
|
2498
2559
|
* Explicit consent to receive marketing email (newsletters, job alerts by
|
|
2499
2560
|
* broadcast, product updates). Marketing sends are HARD-GATED on
|
|
@@ -2505,18 +2566,18 @@ var UserSchema = object33({
|
|
|
2505
2566
|
* unsubscribe sets `subscribed=false` so consent and the email-service
|
|
2506
2567
|
* suppression list stay consistent. Defaults to NOT subscribed (opt-in).
|
|
2507
2568
|
*/
|
|
2508
|
-
marketingConsent:
|
|
2569
|
+
marketingConsent: object34({
|
|
2509
2570
|
subscribed: boolean15().default(false).label("Subscribed to marketing email"),
|
|
2510
2571
|
/** Epoch ms of the last consent change (opt-in or opt-out). */
|
|
2511
|
-
updatedAt:
|
|
2572
|
+
updatedAt: number11().optional().label("Consent updated at"),
|
|
2512
2573
|
/** Where the consent change originated, e.g. `onboarding`, `unsubscribe`, `settings`. */
|
|
2513
|
-
source:
|
|
2574
|
+
source: string32().optional().label("Consent source"),
|
|
2514
2575
|
/**
|
|
2515
2576
|
* How often the user receives job-alert emails. `off` disables job alerts
|
|
2516
2577
|
* only; other marketing stays governed by `subscribed`. Unset means the
|
|
2517
2578
|
* user has not chosen a cadence yet.
|
|
2518
2579
|
*/
|
|
2519
|
-
jobAlertFrequency:
|
|
2580
|
+
jobAlertFrequency: string32().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
|
|
2520
2581
|
}).default({ subscribed: false }).label("Marketing Consent"),
|
|
2521
2582
|
/**
|
|
2522
2583
|
* Per-channel toggles for NOTIFICATION email — mail about activity on the
|
|
@@ -2531,7 +2592,7 @@ var UserSchema = object33({
|
|
|
2531
2592
|
*
|
|
2532
2593
|
* Transactional mail (magic-link, verification) ignores this object entirely.
|
|
2533
2594
|
*/
|
|
2534
|
-
notificationPrefs:
|
|
2595
|
+
notificationPrefs: object34({
|
|
2535
2596
|
groupActivity: boolean15().default(true).label("Group activity email"),
|
|
2536
2597
|
jobActivity: boolean15().default(true).label("Job activity email"),
|
|
2537
2598
|
applicationUpdates: boolean15().default(true).label("Application update email"),
|
|
@@ -2547,15 +2608,15 @@ var UserSchema = object33({
|
|
|
2547
2608
|
* Generated from a structured summary of skills, experience, education, etc.
|
|
2548
2609
|
* Only generated for public profiles with sufficient completeness (≥60%).
|
|
2549
2610
|
*/
|
|
2550
|
-
embedding:
|
|
2551
|
-
vector: array14(
|
|
2552
|
-
model:
|
|
2611
|
+
embedding: object34({
|
|
2612
|
+
vector: array14(number11().required()).optional().label("Embedding vector"),
|
|
2613
|
+
model: string32().optional().label("Embedding model")
|
|
2553
2614
|
}).nullable().optional().label("Embedding")
|
|
2554
2615
|
}).concat(UserGeneralDetailSchema).concat(UserJobPreferencesSchema).concat(UserRecruiterProfileSchema).concat(UserCoordinatorProfileSchema).noUnknown().strict().label("User Schema");
|
|
2555
2616
|
|
|
2556
2617
|
// src/user/user-completeness.schema.ts
|
|
2557
|
-
import { object as
|
|
2558
|
-
var UserCompletenessSchema =
|
|
2618
|
+
import { object as object35 } from "yup";
|
|
2619
|
+
var UserCompletenessSchema = object35({
|
|
2559
2620
|
additionalInfo: CompletenessScoreSchema(0).label("Additional Info"),
|
|
2560
2621
|
// Optional
|
|
2561
2622
|
certifications: CompletenessScoreSchema(0).label("Certifications"),
|
|
@@ -2647,6 +2708,8 @@ export {
|
|
|
2647
2708
|
ResourceType,
|
|
2648
2709
|
SITEMAP_FORMAT,
|
|
2649
2710
|
SYSTEM_DATE_FORMAT,
|
|
2711
|
+
SavedSearchSchema,
|
|
2712
|
+
SavedSearchStatus,
|
|
2650
2713
|
SkillSchema,
|
|
2651
2714
|
SocialAccount,
|
|
2652
2715
|
SocialAccountSchema,
|
|
@@ -2679,6 +2742,7 @@ export {
|
|
|
2679
2742
|
SupportedReportReasons,
|
|
2680
2743
|
SupportedResourceTypes,
|
|
2681
2744
|
SupportedSalaryCurrencies,
|
|
2745
|
+
SupportedSavedSearchStatuses,
|
|
2682
2746
|
SupportedSocialAccounts,
|
|
2683
2747
|
SupportedStudyTypes,
|
|
2684
2748
|
SupportedUserProfileVisibilities,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -62,6 +62,12 @@ export * from "./marketing/marketing.constant.js";
|
|
|
62
62
|
export * from "./pagination/pagination.schema.js";
|
|
63
63
|
export * from "./pagination/pagination.constant.js";
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Saved-search schemas and constants (job alerts).
|
|
67
|
+
*/
|
|
68
|
+
export * from "./saved-search/saved-search.schema.js";
|
|
69
|
+
export * from "./saved-search/saved-search.constant.js";
|
|
70
|
+
|
|
65
71
|
/**
|
|
66
72
|
* Report schemas and constants.
|
|
67
73
|
*/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle of a saved search. Lowercase values, like every other enum in this
|
|
3
|
+
* package: the string is stored in Mongo and compared as a literal across
|
|
4
|
+
* services, so its casing is data rather than style.
|
|
5
|
+
*/
|
|
6
|
+
export enum SavedSearchStatus {
|
|
7
|
+
/** Matching runs and alerts may be sent. */
|
|
8
|
+
Active = "active",
|
|
9
|
+
/**
|
|
10
|
+
* Kept for the user to see and re-enable, but skipped by the matching sweep.
|
|
11
|
+
* Distinct from deleting: a user pausing alerts should not lose the query they
|
|
12
|
+
* spent time building.
|
|
13
|
+
*/
|
|
14
|
+
Paused = "paused",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SupportedSavedSearchStatuses = Object.values(SavedSearchStatus);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { number, object, string } from "yup";
|
|
2
|
+
import { JobAlertFrequency, SupportedJobAlertFrequencies } from "../user/user.constant.js";
|
|
3
|
+
import { SavedSearchStatus, SupportedSavedSearchStatuses } from "./saved-search.constant.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A search a user asked to be kept, and optionally to be alerted about.
|
|
7
|
+
*
|
|
8
|
+
* Two jobs in one document, deliberately: the query the user built, and the
|
|
9
|
+
* cadence at which they want it re-run for them. Splitting them would mean a
|
|
10
|
+
* user with three saved searches and one alert preference has no way to say
|
|
11
|
+
* which search the alert is for.
|
|
12
|
+
*
|
|
13
|
+
* The stored query mirrors what `GET /jobs/search-v3` accepts — a free-text
|
|
14
|
+
* `search` plus a `filter` map — so replaying a saved search is running the same
|
|
15
|
+
* search the user originally ran, not a second, subtly different implementation
|
|
16
|
+
* that drifts from it.
|
|
17
|
+
*/
|
|
18
|
+
export const SavedSearchSchema = object({
|
|
19
|
+
/** Public identifier. Server-assigned. */
|
|
20
|
+
shortId: string().trim().label("Short Id"),
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Owner's Better Auth account id. Server-assigned from the verified token,
|
|
24
|
+
* never accepted from a request body.
|
|
25
|
+
*/
|
|
26
|
+
ownerUserId: string().trim().label("Owner"),
|
|
27
|
+
|
|
28
|
+
/** What the user calls this search, e.g. "Remote React roles". */
|
|
29
|
+
label: string().trim().max(120).required().label("Label"),
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The free-text query. Optional because a search can be filters-only ("all
|
|
33
|
+
* remote jobs in Sweden"), which is a legitimate alert to want.
|
|
34
|
+
*/
|
|
35
|
+
search: string().trim().max(500).optional().label("Search Text"),
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Field filters, in the same `{ field: values }` shape the search endpoint
|
|
39
|
+
* parses from `filter[field]=a,b`. Held as a free-form object rather than a
|
|
40
|
+
* closed schema because the job facets it addresses live in jobs-service and
|
|
41
|
+
* change independently; validating them here would put this package in the
|
|
42
|
+
* business of tracking that vocabulary.
|
|
43
|
+
*/
|
|
44
|
+
filter: object().optional().default({}).label("Filters"),
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* How often to mail matches. `off` keeps the search saved but silent, which is
|
|
48
|
+
* the difference between "I don't want mail" and "delete my search".
|
|
49
|
+
*
|
|
50
|
+
* Reuses the cadence enum from `marketingConsent.jobAlertFrequency` rather
|
|
51
|
+
* than defining a second one: two vocabularies for the same concept is how
|
|
52
|
+
* they drift.
|
|
53
|
+
*/
|
|
54
|
+
frequency: string()
|
|
55
|
+
.oneOf(SupportedJobAlertFrequencies)
|
|
56
|
+
.default(JobAlertFrequency.Off)
|
|
57
|
+
.label("Alert Frequency"),
|
|
58
|
+
|
|
59
|
+
status: string()
|
|
60
|
+
.oneOf(SupportedSavedSearchStatuses)
|
|
61
|
+
.default(SavedSearchStatus.Active)
|
|
62
|
+
.label("Status"),
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Epoch ms of the last alert sent for this search. The matching sweep reads it
|
|
66
|
+
* to decide whether the cadence is due, and writes it only AFTER a successful
|
|
67
|
+
* publish, so a failed run retries next tick rather than silently skipping a
|
|
68
|
+
* period.
|
|
69
|
+
*/
|
|
70
|
+
lastNotifiedAt: number().optional().label("Last Notified At"),
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Epoch ms high-water mark: only jobs created after this are "new" for the
|
|
74
|
+
* next alert. Kept separate from `lastNotifiedAt` because a run that finds no
|
|
75
|
+
* matches should still advance what counts as new, without implying mail was
|
|
76
|
+
* sent.
|
|
77
|
+
*/
|
|
78
|
+
lastMatchedAt: number().optional().label("Last Matched At"),
|
|
79
|
+
})
|
|
80
|
+
// `createdAt`/`updatedAt` are deliberately absent, matching every other schema
|
|
81
|
+
// here: audit timestamps are the storing service's concern, not part of the
|
|
82
|
+
// shape a client sends or validates.
|
|
83
|
+
.noUnknown()
|
|
84
|
+
.strict();
|