@thejob/schema 2.0.0 → 2.0.2
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/scheduled_tasks.lock +1 -0
- package/.claude/settings.local.json +138 -2
- package/dist/index.cjs +551 -146
- package/dist/index.d.cts +593 -89
- package/dist/index.d.ts +593 -89
- package/dist/index.js +514 -143
- package/package.json +1 -1
- package/src/common/common.constant.ts +30 -0
- package/src/common/common.schema.ts +14 -0
- package/src/extensions/date-string.extension.ts +1 -1
- package/src/index.ts +17 -0
- package/src/job/job.constant.ts +66 -0
- package/src/job/job.schema.ts +245 -0
- package/src/question/choice-question.schema.ts +96 -0
- package/src/question/index.ts +6 -0
- package/src/question/input-question.schema.ts +24 -0
- package/src/question/question.constant.ts +14 -0
- package/src/question/question.schema.ts +22 -0
- package/src/question/question.utils.ts +23 -0
- package/src/question/read-and-acknowledge.schema.ts +25 -0
- package/src/report/report.constant.ts +20 -0
- package/src/report/report.schema.ts +20 -0
- package/src/social-account/social-account.schema.ts +1 -1
- package/src/user/user-coordinator-profile.schema.ts +1 -1
- package/src/user/user-job-preferences.schema.ts +11 -11
- package/src/user/user-recruiter-profile.schema.ts +1 -1
- package/src/user/user.constant.ts +1 -1
package/package.json
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common option keys shared across schemas. `Yes`/`No` are used by acknowledge
|
|
3
|
+
* questions; `Skill`/`EducationLevel`/`ExperienceLevel`/etc. tag where a choice
|
|
4
|
+
* question sources its options from. Migrated from schema v1.
|
|
5
|
+
*/
|
|
6
|
+
export enum Common {
|
|
7
|
+
Yes = "yes",
|
|
8
|
+
No = "no",
|
|
9
|
+
DateRangeOption = "date_range_option",
|
|
10
|
+
ExperienceLevel = "experience_level",
|
|
11
|
+
Company = "company",
|
|
12
|
+
EmploymentType = "employment_type",
|
|
13
|
+
WorkMode = "work_mode",
|
|
14
|
+
Location = "location",
|
|
15
|
+
CareerLevel = "career_level",
|
|
16
|
+
Category = "category",
|
|
17
|
+
Designation = "designation",
|
|
18
|
+
EducationLevel = "education_level",
|
|
19
|
+
Language = "language",
|
|
20
|
+
Skill = "skill",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Common constants
|
|
24
|
+
export const EMPTY_STRING = "eMpTyStRiNg";
|
|
25
|
+
// DayJS formats: https://day.js.org/docs/en/display/format
|
|
26
|
+
export const SYSTEM_DATE_FORMAT = "YYYY-MM-DD";
|
|
27
|
+
export const DISPLAY_DATE_FORMAT = "LL";
|
|
28
|
+
export const DISPLAY_DATE_FORMAT_SHORT = "MMMM, YYYY";
|
|
29
|
+
export const SITEMAP_FORMAT = "YYYY-MM-DDThh:mm:ssZ";
|
|
30
|
+
|
|
1
31
|
// Education-related enums
|
|
2
32
|
export enum StudyType {
|
|
3
33
|
FullTime = "full_time",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { boolean, number, object, string } from "yup";
|
|
2
|
+
import type { InferType } from "yup";
|
|
2
3
|
|
|
3
4
|
export const TermsAcceptedSchema = object({
|
|
4
5
|
termsAccepted: boolean()
|
|
@@ -26,3 +27,16 @@ export const CompletenessScoreSchema = (minScore: number = 50) =>
|
|
|
26
27
|
totalSteps: number().min(0).default(0).label("Total Steps"),
|
|
27
28
|
completedSteps: number().min(0).default(0).label("Completed Steps"),
|
|
28
29
|
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tracks a user reference plus when the relation was created (bookmarks,
|
|
33
|
+
* applicants, reports on a job). The v1 `userId` used `.objectId()`; plain
|
|
34
|
+
* `string()` in v2.
|
|
35
|
+
*/
|
|
36
|
+
export const UserIdAndCreatedAtSchema = object().shape({
|
|
37
|
+
userId: string().required().label("User Id"),
|
|
38
|
+
createdAt: string().required().label("Created At"),
|
|
39
|
+
});
|
|
40
|
+
export type TUserIdAndCreatedAtSchema = InferType<
|
|
41
|
+
typeof UserIdAndCreatedAtSchema
|
|
42
|
+
>;
|
|
@@ -10,7 +10,7 @@ export class DateStringSchema<
|
|
|
10
10
|
// Report as the standard yup "mixed" type so tools that introspect the
|
|
11
11
|
// schema via `.describe()` (e.g. sveltekit-superforms' JSON-schema
|
|
12
12
|
// converter) recognize it. The class still inherits all MixedSchema
|
|
13
|
-
// behavior
|
|
13
|
+
// behavior - only the `type` label changes.
|
|
14
14
|
super({
|
|
15
15
|
type: "mixed",
|
|
16
16
|
check: (value): value is NonNullable<TType> =>
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,17 @@ export * from "./extensions/date-string.extension.js";
|
|
|
20
20
|
export * from "./group/group.schema.js";
|
|
21
21
|
export * from "./group/group.constant.js";
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Job schemas and constants.
|
|
25
|
+
*/
|
|
26
|
+
export * from "./job/job.schema.js";
|
|
27
|
+
export * from "./job/job.constant.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Question schemas and constants (job questionnaire).
|
|
31
|
+
*/
|
|
32
|
+
export * from "./question/index.js";
|
|
33
|
+
|
|
23
34
|
/**
|
|
24
35
|
* Location schemas and constants.
|
|
25
36
|
*/
|
|
@@ -37,6 +48,12 @@ export * from "./page/page.constant.js";
|
|
|
37
48
|
export * from "./pagination/pagination.schema.js";
|
|
38
49
|
export * from "./pagination/pagination.constant.js";
|
|
39
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Report schemas and constants.
|
|
53
|
+
*/
|
|
54
|
+
export * from "./report/report.schema.js";
|
|
55
|
+
export * from "./report/report.constant.js";
|
|
56
|
+
|
|
40
57
|
/**
|
|
41
58
|
* Skill schemas and constants.
|
|
42
59
|
*/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export enum JobStatus {
|
|
2
|
+
Published = "published",
|
|
3
|
+
Draft = "draft",
|
|
4
|
+
Deleted = "deleted",
|
|
5
|
+
Closed = "closed",
|
|
6
|
+
Expired = "expired",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const SupportedJobStatuses = Object.values(JobStatus);
|
|
10
|
+
|
|
11
|
+
export enum EmploymentType {
|
|
12
|
+
Fulltime = "full_time",
|
|
13
|
+
PartTime = "part_time",
|
|
14
|
+
Temporary = "temporary",
|
|
15
|
+
Contract = "contract",
|
|
16
|
+
Internship = "internship",
|
|
17
|
+
Freelance = "freelance",
|
|
18
|
+
Volunteer = "volunteer",
|
|
19
|
+
PerDiem = "per_diem",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SupportedEmploymentTypes = Object.values(EmploymentType);
|
|
23
|
+
|
|
24
|
+
export enum WorkMode {
|
|
25
|
+
OnSite = "on_site",
|
|
26
|
+
Hybrid = "hybrid",
|
|
27
|
+
Remote = "remote",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const SupportedWorkModes = Object.values(WorkMode);
|
|
31
|
+
|
|
32
|
+
export enum CompensationType {
|
|
33
|
+
Hourly = "hourly",
|
|
34
|
+
Weekly = "weekly",
|
|
35
|
+
Monthly = "monthly",
|
|
36
|
+
Yearly = "yearly",
|
|
37
|
+
OneTime = "one-time",
|
|
38
|
+
Other = "other",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const SupportedCompensationTypes = Object.values(CompensationType);
|
|
42
|
+
|
|
43
|
+
export enum EmployeeCount {
|
|
44
|
+
_1_2 = "1_2_employees",
|
|
45
|
+
_3_10 = "3_10_employees",
|
|
46
|
+
_11_100 = "11_100_employees",
|
|
47
|
+
_101_1000 = "101_1000_employees",
|
|
48
|
+
_1001_5000 = "1001_5000_employees",
|
|
49
|
+
_5001_10000 = "5001_10000_employees",
|
|
50
|
+
_10001_Plus = "10001_plus_employees",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const SupportedEmployeeCounts = Object.values(EmployeeCount);
|
|
54
|
+
|
|
55
|
+
export enum ApplicationReceivePreference {
|
|
56
|
+
Inbox = "inbox",
|
|
57
|
+
Email = "email",
|
|
58
|
+
ExternalWebsite = "external_website",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const SupportedApplicationReceivePreferences = Object.values(
|
|
62
|
+
ApplicationReceivePreference,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Note: ExperienceLevel lives in common.constant.ts (single source of truth);
|
|
66
|
+
// it is intentionally not re-declared here.
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { array, boolean, lazy, mixed, number, object, string } from "yup";
|
|
2
|
+
import type { InferType, ObjectSchema } from "yup";
|
|
3
|
+
import {
|
|
4
|
+
DbDefaultSchema,
|
|
5
|
+
UserIdAndCreatedAtSchema,
|
|
6
|
+
} from "../common/common.schema.js";
|
|
7
|
+
import {
|
|
8
|
+
SupportedEducationLevels,
|
|
9
|
+
SupportedExperienceLevels,
|
|
10
|
+
SYSTEM_DATE_FORMAT,
|
|
11
|
+
} from "../common/common.constant.js";
|
|
12
|
+
import { DurationSchema } from "../duration/duration.schema.js";
|
|
13
|
+
import { LocationSchema } from "../location/location.schema.js";
|
|
14
|
+
import { PageSchema } from "../page/page.schema.js";
|
|
15
|
+
import { SkillSchema } from "../skill/skill.schema.js";
|
|
16
|
+
import { QuestionType } from "../question/index.js";
|
|
17
|
+
import type {
|
|
18
|
+
TChoiceQuestionSchema,
|
|
19
|
+
TInputQuestionSchema,
|
|
20
|
+
TQuestionSchema,
|
|
21
|
+
TReadAndAcknowledgeQuestionSchema,
|
|
22
|
+
} from "../question/index.js";
|
|
23
|
+
import { getSchemaByQuestion } from "../question/question.utils.js";
|
|
24
|
+
import {
|
|
25
|
+
ApplicationReceivePreference,
|
|
26
|
+
JobStatus,
|
|
27
|
+
SupportedCompensationTypes,
|
|
28
|
+
SupportedJobStatuses,
|
|
29
|
+
} from "./job.constant.js";
|
|
30
|
+
|
|
31
|
+
export const JobSchema = object({
|
|
32
|
+
/** Unique id usable by external systems to reference the job. */
|
|
33
|
+
uniqueId: string().optional().label("Unique ID"),
|
|
34
|
+
|
|
35
|
+
/** Job title shown in listings and search. */
|
|
36
|
+
headline: string().required().min(5).max(150).label("Headline"),
|
|
37
|
+
|
|
38
|
+
/** User-friendly, unique URL identifier. */
|
|
39
|
+
slug: string().required().max(250).label("Slug"),
|
|
40
|
+
|
|
41
|
+
/** How applications are received (inbox vs external website). */
|
|
42
|
+
applicationReceivePreference: mixed<ApplicationReceivePreference>()
|
|
43
|
+
.oneOf([
|
|
44
|
+
ApplicationReceivePreference.Inbox,
|
|
45
|
+
ApplicationReceivePreference.ExternalWebsite,
|
|
46
|
+
])
|
|
47
|
+
.required()
|
|
48
|
+
.label("Preference"),
|
|
49
|
+
|
|
50
|
+
/** Required when applicationReceivePreference is ExternalWebsite. */
|
|
51
|
+
externalApplyLink: string().when("applicationReceivePreference", {
|
|
52
|
+
is: (value: ApplicationReceivePreference) =>
|
|
53
|
+
value === ApplicationReceivePreference.ExternalWebsite,
|
|
54
|
+
then: (schema) => schema.url().required().label("Apply for Job link"),
|
|
55
|
+
otherwise: (schema) => schema.optional(),
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
/** Company the job belongs to (subset of PageSchema). */
|
|
59
|
+
company: PageSchema.pick(["name", "slug", "type", "logo"])
|
|
60
|
+
.deepPartial()
|
|
61
|
+
.concat(PageSchema.pick(["name", "type"]))
|
|
62
|
+
.required()
|
|
63
|
+
.label("Company"),
|
|
64
|
+
|
|
65
|
+
/** @deprecated kept for backwards compatibility. */
|
|
66
|
+
designation: string().optional().nullable().label("Designation"),
|
|
67
|
+
|
|
68
|
+
employmentType: string().required().label("Employment type"),
|
|
69
|
+
|
|
70
|
+
workMode: string().required().label("Work mode"),
|
|
71
|
+
|
|
72
|
+
skills: array()
|
|
73
|
+
.of(SkillSchema.pick(["name", "logo"]).required().label("Skill"))
|
|
74
|
+
.nullable()
|
|
75
|
+
.optional()
|
|
76
|
+
.label("Skills"),
|
|
77
|
+
|
|
78
|
+
bookmarks: array().of(UserIdAndCreatedAtSchema).optional().label("Bookmarks"),
|
|
79
|
+
|
|
80
|
+
embedding: object({
|
|
81
|
+
vector: array(number().required()).optional().label("Embedding vector"),
|
|
82
|
+
model: string().optional().label("Embedding model"),
|
|
83
|
+
})
|
|
84
|
+
.nullable()
|
|
85
|
+
.optional()
|
|
86
|
+
.label("Embedding"),
|
|
87
|
+
|
|
88
|
+
applicants: array()
|
|
89
|
+
.of(UserIdAndCreatedAtSchema)
|
|
90
|
+
.optional()
|
|
91
|
+
.label("Applicants"),
|
|
92
|
+
|
|
93
|
+
applicantCount: number().optional().label("Applicant count"),
|
|
94
|
+
|
|
95
|
+
externalApplicationLinkClickCount: number()
|
|
96
|
+
.optional()
|
|
97
|
+
.label("External application link click count"),
|
|
98
|
+
|
|
99
|
+
description: string().required().min(100).max(50000).label("Description"),
|
|
100
|
+
|
|
101
|
+
descriptionMarkdown: string().required().label("Description Markdown"),
|
|
102
|
+
|
|
103
|
+
descriptionHTML: string().optional().label("Description HTML"),
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Job locations. Required even for remote roles (compliance/legal). Defaults
|
|
107
|
+
* to an empty array; min(1) is intentionally not enforced yet.
|
|
108
|
+
*/
|
|
109
|
+
locations: array().of(LocationSchema).default([]).label("Location"),
|
|
110
|
+
|
|
111
|
+
educationLevel: array(string().oneOf(SupportedEducationLevels))
|
|
112
|
+
.required()
|
|
113
|
+
.min(1)
|
|
114
|
+
.label("Educational level"),
|
|
115
|
+
|
|
116
|
+
/** Posting validity window (start/expiry). */
|
|
117
|
+
validity: DurationSchema({
|
|
118
|
+
format: SYSTEM_DATE_FORMAT,
|
|
119
|
+
startLabel: "Start Date",
|
|
120
|
+
endLabel: "Expiry Date",
|
|
121
|
+
}).label("Validity"),
|
|
122
|
+
|
|
123
|
+
experienceLevel: string()
|
|
124
|
+
.oneOf(SupportedExperienceLevels)
|
|
125
|
+
.nullable()
|
|
126
|
+
.optional()
|
|
127
|
+
.label("Experience level"),
|
|
128
|
+
|
|
129
|
+
contactEmail: string().email().nullable().optional().label("Contact email"),
|
|
130
|
+
|
|
131
|
+
workingHoursPerWeek: number()
|
|
132
|
+
.nullable()
|
|
133
|
+
.optional()
|
|
134
|
+
.min(1)
|
|
135
|
+
.max(24 * 7)
|
|
136
|
+
.label("Working hours per week"),
|
|
137
|
+
|
|
138
|
+
tags: array().max(50).optional().label("Tags"),
|
|
139
|
+
|
|
140
|
+
/** Direct/Quick-apply questionnaire. Each item is validated against the
|
|
141
|
+
* concrete question schema for its `type`, with the stored `answers` omitted. */
|
|
142
|
+
questionnaire: array()
|
|
143
|
+
.of(
|
|
144
|
+
lazy((question: TQuestionSchema) => {
|
|
145
|
+
const schema = getSchemaByQuestion(question.type);
|
|
146
|
+
if (question.type === QuestionType.Input) {
|
|
147
|
+
return (schema as ObjectSchema<TInputQuestionSchema>).omit([
|
|
148
|
+
"answers",
|
|
149
|
+
]);
|
|
150
|
+
} else if (question.type === QuestionType.Choice) {
|
|
151
|
+
return (schema as ObjectSchema<TChoiceQuestionSchema>).omit([
|
|
152
|
+
"answers",
|
|
153
|
+
]);
|
|
154
|
+
} else if (question.type === QuestionType.ReadAndAcknowledge) {
|
|
155
|
+
return (
|
|
156
|
+
schema as ObjectSchema<TReadAndAcknowledgeQuestionSchema>
|
|
157
|
+
).omit(["answers"]);
|
|
158
|
+
}
|
|
159
|
+
return schema;
|
|
160
|
+
}),
|
|
161
|
+
)
|
|
162
|
+
.label("Questionnaire"),
|
|
163
|
+
|
|
164
|
+
status: string()
|
|
165
|
+
.oneOf(SupportedJobStatuses)
|
|
166
|
+
.default(JobStatus.Draft)
|
|
167
|
+
.required()
|
|
168
|
+
.label("Status"),
|
|
169
|
+
|
|
170
|
+
seoTags: object({
|
|
171
|
+
description: string().optional().label("Description"),
|
|
172
|
+
keywords: array().of(string()).optional().label("Keywords"),
|
|
173
|
+
})
|
|
174
|
+
.nullable()
|
|
175
|
+
.optional()
|
|
176
|
+
.label("SEO Tags"),
|
|
177
|
+
|
|
178
|
+
jdSummary: string().optional().label("JD Summary"),
|
|
179
|
+
|
|
180
|
+
canCreateAlert: boolean().optional().label("Can create alert"),
|
|
181
|
+
|
|
182
|
+
canDirectApply: boolean().optional().label("Can direct apply"),
|
|
183
|
+
|
|
184
|
+
hasApplied: boolean().optional().label("Has applied"),
|
|
185
|
+
|
|
186
|
+
isOwner: boolean().optional().label("Is owner"),
|
|
187
|
+
|
|
188
|
+
hasBookmarked: boolean().optional().label("Has bookmarked"),
|
|
189
|
+
|
|
190
|
+
hasReported: boolean().optional().label("Has reported"),
|
|
191
|
+
|
|
192
|
+
ratePerHour: number().optional().min(0).label("Rate per hour"),
|
|
193
|
+
|
|
194
|
+
isPromoted: boolean().optional().label("Is promoted"),
|
|
195
|
+
|
|
196
|
+
salaryRange: object({
|
|
197
|
+
currency: string().required().label("Currency").default("USD"),
|
|
198
|
+
min: number().nullable().optional().min(0).label("Minimum Salary"),
|
|
199
|
+
max: number().nullable().optional().min(0).label("Maximum Salary"),
|
|
200
|
+
compensationType: string()
|
|
201
|
+
.oneOf(SupportedCompensationTypes)
|
|
202
|
+
.nullable()
|
|
203
|
+
.optional()
|
|
204
|
+
.label("Compensation Type"),
|
|
205
|
+
isNegotiable: boolean()
|
|
206
|
+
.nullable()
|
|
207
|
+
.optional()
|
|
208
|
+
.default(true)
|
|
209
|
+
.label("Is Negotiable"),
|
|
210
|
+
})
|
|
211
|
+
.nullable()
|
|
212
|
+
.optional()
|
|
213
|
+
.label("Salary Range"),
|
|
214
|
+
|
|
215
|
+
reports: array().of(UserIdAndCreatedAtSchema).optional().label("Reports"),
|
|
216
|
+
|
|
217
|
+
category: string().nullable().optional().label("Category"),
|
|
218
|
+
|
|
219
|
+
industry: string().nullable().optional().label("Industry"),
|
|
220
|
+
|
|
221
|
+
subCategories: array()
|
|
222
|
+
.of(string().trim().required())
|
|
223
|
+
.nullable()
|
|
224
|
+
.optional()
|
|
225
|
+
.label("Sub Categories"),
|
|
226
|
+
|
|
227
|
+
termsAccepted: boolean()
|
|
228
|
+
.oneOf([true], "Accept terms before proceeding")
|
|
229
|
+
.required("Accept terms before proceeding")
|
|
230
|
+
.label("Terms accepted"),
|
|
231
|
+
|
|
232
|
+
isFeatured: boolean().optional().default(false).label("Is featured"),
|
|
233
|
+
|
|
234
|
+
/** Market country codes to feature this job in; empty = global. */
|
|
235
|
+
featuredMarkets: array()
|
|
236
|
+
.of(string().required())
|
|
237
|
+
.optional()
|
|
238
|
+
.default([])
|
|
239
|
+
.label("Featured markets"),
|
|
240
|
+
})
|
|
241
|
+
.concat(DbDefaultSchema)
|
|
242
|
+
.noUnknown()
|
|
243
|
+
.label("Job");
|
|
244
|
+
|
|
245
|
+
export type TJobSchema = InferType<typeof JobSchema>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { array, mixed, object, string } from "yup";
|
|
2
|
+
import type { InferType } from "yup";
|
|
3
|
+
import { Common } from "../common/common.constant.js";
|
|
4
|
+
import {
|
|
5
|
+
AnswerChoiceType,
|
|
6
|
+
QuestionType,
|
|
7
|
+
SupportedAnswerChoiceTypes,
|
|
8
|
+
} from "./question.constant.js";
|
|
9
|
+
import { QuestionSchema } from "./question.schema.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validates that the selected answer(s) come from the question's own `options`.
|
|
13
|
+
* In v2 options and answers are plain strings (the v1 name/id/logo and "others"
|
|
14
|
+
* reference-object variants were dropped, matching the rest of the v2 schema).
|
|
15
|
+
*/
|
|
16
|
+
const ChoicePreferredAnswerSchema = (label: string = "Preferred answer") =>
|
|
17
|
+
mixed<string | string[]>().when("answerChoiceType", {
|
|
18
|
+
is: (value: AnswerChoiceType) => value === AnswerChoiceType.Multiple,
|
|
19
|
+
then: () =>
|
|
20
|
+
array(string())
|
|
21
|
+
.test(
|
|
22
|
+
"is-valid-option",
|
|
23
|
+
`${label} must be from the provided options`,
|
|
24
|
+
function (answer) {
|
|
25
|
+
const { options = [] } = this.parent;
|
|
26
|
+
if (answer && answer.length > 0) {
|
|
27
|
+
return answer.every((a) => options.includes(a));
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
.optional()
|
|
33
|
+
.label(label),
|
|
34
|
+
otherwise: () =>
|
|
35
|
+
string()
|
|
36
|
+
.test(
|
|
37
|
+
"is-valid-option",
|
|
38
|
+
`${label} must be from the provided options`,
|
|
39
|
+
function (answer) {
|
|
40
|
+
const { options = [] } = this.parent;
|
|
41
|
+
return !answer || options.includes(answer);
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
.optional()
|
|
45
|
+
.label(label),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const getChoiceAnswerSchema = (
|
|
49
|
+
answerChoiceType: AnswerChoiceType,
|
|
50
|
+
isOptional: boolean,
|
|
51
|
+
) => {
|
|
52
|
+
if (answerChoiceType === AnswerChoiceType.Multiple) {
|
|
53
|
+
const multiAnswerSchema = array(string().max(500));
|
|
54
|
+
return isOptional
|
|
55
|
+
? multiAnswerSchema.optional().default([])
|
|
56
|
+
: multiAnswerSchema
|
|
57
|
+
.min(1, "Select at least one option.")
|
|
58
|
+
.required("Select at least one option.")
|
|
59
|
+
.default([]);
|
|
60
|
+
}
|
|
61
|
+
// Single choice
|
|
62
|
+
const singleAnswerSchema = string().max(500);
|
|
63
|
+
return isOptional
|
|
64
|
+
? singleAnswerSchema.optional()
|
|
65
|
+
: singleAnswerSchema.required("Select an option.");
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const ChoiceQuestionSchema = QuestionSchema.concat(
|
|
69
|
+
object().shape({
|
|
70
|
+
type: string()
|
|
71
|
+
.oneOf([QuestionType.Choice])
|
|
72
|
+
.required()
|
|
73
|
+
.label("Question type"),
|
|
74
|
+
optionsFrom: string()
|
|
75
|
+
.oneOf([Common.Skill, Common.EducationLevel, Common.ExperienceLevel])
|
|
76
|
+
.optional()
|
|
77
|
+
.label("Option field"),
|
|
78
|
+
options: array(string().required())
|
|
79
|
+
.min(1)
|
|
80
|
+
.required()
|
|
81
|
+
.label("Options"),
|
|
82
|
+
answerChoiceType: string()
|
|
83
|
+
.oneOf(SupportedAnswerChoiceTypes)
|
|
84
|
+
.required()
|
|
85
|
+
.default(AnswerChoiceType.Single)
|
|
86
|
+
.label("Type"),
|
|
87
|
+
preferredAnswer: ChoicePreferredAnswerSchema("Preferred answer"),
|
|
88
|
+
answers: mixed<string | string[]>().when(
|
|
89
|
+
["answerChoiceType", "isOptional"],
|
|
90
|
+
([answerChoiceType, isOptional]) =>
|
|
91
|
+
getChoiceAnswerSchema(answerChoiceType, isOptional),
|
|
92
|
+
),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
export type TChoiceQuestionSchema = InferType<typeof ChoiceQuestionSchema>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { object, string } from "yup";
|
|
2
|
+
import type { InferType } from "yup";
|
|
3
|
+
import { QuestionType } from "./question.constant.js";
|
|
4
|
+
import { QuestionSchema } from "./question.schema.js";
|
|
5
|
+
|
|
6
|
+
export const InputQuestionSchema = QuestionSchema.concat(
|
|
7
|
+
object().shape({
|
|
8
|
+
type: string()
|
|
9
|
+
.oneOf([QuestionType.Input])
|
|
10
|
+
.required()
|
|
11
|
+
.label("Question type"),
|
|
12
|
+
preferredAnswer: string().max(100).optional().label("Preferred Answer"),
|
|
13
|
+
answers: string()
|
|
14
|
+
.max(500)
|
|
15
|
+
.when("isOptional", {
|
|
16
|
+
is: (isOptional: boolean) => Boolean(isOptional),
|
|
17
|
+
then: (schema) => schema.optional(),
|
|
18
|
+
otherwise: (schema) => schema.required(),
|
|
19
|
+
})
|
|
20
|
+
.label("Answer"),
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export type TInputQuestionSchema = InferType<typeof InputQuestionSchema>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export enum QuestionType {
|
|
2
|
+
Input = "input",
|
|
3
|
+
Choice = "choice",
|
|
4
|
+
ReadAndAcknowledge = "readAndAcknowledge",
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const SupportedQuestionTypes = Object.values(QuestionType);
|
|
8
|
+
|
|
9
|
+
export enum AnswerChoiceType {
|
|
10
|
+
Single = "single",
|
|
11
|
+
Multiple = "multiple",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const SupportedAnswerChoiceTypes = Object.values(AnswerChoiceType);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { boolean, object, string } from "yup";
|
|
2
|
+
import { SupportedQuestionTypes } from "./question.constant.js";
|
|
3
|
+
import type { TChoiceQuestionSchema } from "./choice-question.schema.js";
|
|
4
|
+
import type { TInputQuestionSchema } from "./input-question.schema.js";
|
|
5
|
+
import type { TReadAndAcknowledgeQuestionSchema } from "./read-and-acknowledge.schema.js";
|
|
6
|
+
|
|
7
|
+
/** Base fields shared by every question variant. The concrete variants concat
|
|
8
|
+
* their own `type`-narrowed fields onto this (see input/choice/acknowledge). */
|
|
9
|
+
export const QuestionSchema = object().shape({
|
|
10
|
+
label: string().max(50).optional().label("Label"),
|
|
11
|
+
default: boolean().optional().default(false).label("Default"),
|
|
12
|
+
title: string().required().max(200).label("Title"),
|
|
13
|
+
type: string().oneOf(SupportedQuestionTypes).required().label("Type"),
|
|
14
|
+
isOptional: boolean().optional().label("Mark if the question is optional"),
|
|
15
|
+
readonly: boolean().optional().label("Readonly"),
|
|
16
|
+
isNew: boolean().optional().label("Is New"),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type TQuestionSchema =
|
|
20
|
+
| TInputQuestionSchema
|
|
21
|
+
| TChoiceQuestionSchema
|
|
22
|
+
| TReadAndAcknowledgeQuestionSchema;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ObjectSchema } from "yup";
|
|
2
|
+
import { ChoiceQuestionSchema } from "./choice-question.schema.js";
|
|
3
|
+
import { InputQuestionSchema } from "./input-question.schema.js";
|
|
4
|
+
import { QuestionType } from "./question.constant.js";
|
|
5
|
+
import type { TQuestionSchema } from "./question.schema.js";
|
|
6
|
+
import { ReadAndAcknowledgeQuestionSchema } from "./read-and-acknowledge.schema.js";
|
|
7
|
+
|
|
8
|
+
/** Resolve the concrete question schema for a question's `type`. Drives the
|
|
9
|
+
* `lazy` validation of the job questionnaire. */
|
|
10
|
+
export const getSchemaByQuestion = (
|
|
11
|
+
type: QuestionType,
|
|
12
|
+
): ObjectSchema<TQuestionSchema> => {
|
|
13
|
+
switch (type) {
|
|
14
|
+
case QuestionType.Input:
|
|
15
|
+
return InputQuestionSchema as unknown as ObjectSchema<TQuestionSchema>;
|
|
16
|
+
case QuestionType.Choice:
|
|
17
|
+
return ChoiceQuestionSchema as unknown as ObjectSchema<TQuestionSchema>;
|
|
18
|
+
case QuestionType.ReadAndAcknowledge:
|
|
19
|
+
return ReadAndAcknowledgeQuestionSchema as unknown as ObjectSchema<TQuestionSchema>;
|
|
20
|
+
default:
|
|
21
|
+
throw new Error("Unsupported schema: " + type);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { object, string } from "yup";
|
|
2
|
+
import type { InferType } from "yup";
|
|
3
|
+
import { Common } from "../common/common.constant.js";
|
|
4
|
+
import { QuestionType } from "./question.constant.js";
|
|
5
|
+
import { QuestionSchema } from "./question.schema.js";
|
|
6
|
+
|
|
7
|
+
const AcknowledgeAnswerSchema = string()
|
|
8
|
+
.oneOf([Common.Yes, Common.No])
|
|
9
|
+
.required("This question must be acknowledged.");
|
|
10
|
+
|
|
11
|
+
export const ReadAndAcknowledgeQuestionSchema = QuestionSchema.concat(
|
|
12
|
+
object().shape({
|
|
13
|
+
type: string()
|
|
14
|
+
.oneOf([QuestionType.ReadAndAcknowledge])
|
|
15
|
+
.required()
|
|
16
|
+
.label("Question type"),
|
|
17
|
+
description: string().max(10000).required().label("Content"),
|
|
18
|
+
preferredAnswer: AcknowledgeAnswerSchema.label("Preferred Answer"),
|
|
19
|
+
answers: AcknowledgeAnswerSchema.label("Answer"),
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export type TReadAndAcknowledgeQuestionSchema = InferType<
|
|
24
|
+
typeof ReadAndAcknowledgeQuestionSchema
|
|
25
|
+
>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export enum ResourceType {
|
|
2
|
+
Job = "job",
|
|
3
|
+
User = "user",
|
|
4
|
+
Resume = "resume",
|
|
5
|
+
Company = "company",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const SupportedResourceTypes = Object.values(ResourceType);
|
|
9
|
+
|
|
10
|
+
export enum ReportReason {
|
|
11
|
+
OffensiveOrHarassing = "offensive_or_harassing",
|
|
12
|
+
JobExpired = "job_expired",
|
|
13
|
+
AskingMoney = "asking_money",
|
|
14
|
+
FakeJob = "fake_job",
|
|
15
|
+
IncorrectJobDetails = "incorrect_job_details",
|
|
16
|
+
SellingSomething = "selling_something",
|
|
17
|
+
Other = "other",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const SupportedReportReasons = Object.values(ReportReason);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { mixed, object, string } from "yup";
|
|
2
|
+
import {
|
|
3
|
+
ReportReason,
|
|
4
|
+
ResourceType,
|
|
5
|
+
SupportedReportReasons,
|
|
6
|
+
SupportedResourceTypes,
|
|
7
|
+
} from "./report.constant.js";
|
|
8
|
+
|
|
9
|
+
export const ReportSchema = object({
|
|
10
|
+
type: mixed<ResourceType>()
|
|
11
|
+
.oneOf(SupportedResourceTypes)
|
|
12
|
+
.required()
|
|
13
|
+
.label("Type"),
|
|
14
|
+
resourceId: string().required().label("Resource ID"),
|
|
15
|
+
reason: mixed<ReportReason>()
|
|
16
|
+
.oneOf(SupportedReportReasons)
|
|
17
|
+
.required("Please choose a reason")
|
|
18
|
+
.label("Reason"),
|
|
19
|
+
comment: string().max(1000).optional().label("Comment"),
|
|
20
|
+
}).label("Report Schema");
|