Files
Reactive-Resume/src/schema/jobs.ts
T
Luka Fagundes 3e16586d7a feat(jobs): add job listings with AI-powered resume tailoring (#2788)
* feat: add job listings feature with JSearch API integration, resume tailoring, and per-user rate limiting

* feat(jobs): add search filters UI, filter helper functions with tests, and job_search_quota DB migration

* feat(jobs): add pagination with 30 results per page and prev/next navigation

* refactor(job-detail): Adjust sheet width and scroll area height

* feat(ai): Add resume tailoring feature and prompt

* refactor(ai): Revise tailoring prompts and schema for full skill rewrite

* feat(ai): Add reference tailoring and output sanitization

* feat(testing): Add Vitest testing framework

* fix: address PR review - atomic rate limiting, calendar-month quota, skill sync warning, gitignore routeTree.gen.ts

* feat(jobs): Add location filter to job listings

* feat(job-listings): Add DOCX document generation

* feat(job-listings): Enable search by location and on Enter key

* feat(job-listings): Split location filter into city, state, and country

* feat(jobs): Implement job search adapter and JSearch

* Update 'locale/' directory

* feat(resume): Simplify filename generation and add tests

* fix(JSearch): reduce JSearch API usage to 1 request per search to prevent quota exhaustion

* fix(JSearch): Displayed quota amounts on Job Search functionality and settings fixed to pull from RapidAPI/JSearch response

* fix(internal rate limit): Removed internal rate limit and .env.example addition, cloud based implementation handles.

* style(job-filters): Adjust layout of switch filters

* fix(typecheck): Fixed typecheck issues introduced to sync with origin

* feat(jobs): Enhance tailor dialog with apply link and tags

* feat(locale files): updated locale files with the latest build

* feat(jobs): Add job search provider and integrate testing functionality

- Introduced `createJobSearchProvider` function to instantiate a JSearchProvider.
- Enhanced job search provider with methods for searching jobs, retrieving job details, and testing connection.
- Updated `vite.config.ts` to include new testing configurations and plugins.
- Added new dependencies in `package.json` for testing and document generation.
- Removed obsolete `vitest.config.ts` file.
- Improved job search provider tests for better coverage and reliability.

* refactor: Update job search routes and remove obsolete test configurations

- Removed the test configuration from `vite.config.ts`.
- Updated localization files to reflect changes in job search routes, renaming references from `jobs` to `job-search` across multiple languages.
- Adjusted autofix workflow to run formatting without the `--fix` flag for better control over code style adjustments.

* chore: Update dependencies and improve animation performance

- Added `jsdom` as a new dependency in `package.json`.
- Updated `vite-plus` and `vitest` to the latest versions for better compatibility.
- Enhanced animation components with `willChange` styles to optimize rendering performance.
- Adjusted various UI components to improve responsiveness and visual effects.
- Removed obsolete job details functionality from the job search provider and related tests.

* chore(locales): Update localization files for job search improvements

- Modified job search related strings to remove references to "this month" for a more concise format.
- Updated file references in localization entries to reflect changes in the job search component structure.
- Added new strings for API usage, quota remaining, and job fetching error messages across multiple languages.
- Removed obsolete "Monthly Usage" string from localization files.

* chore(dependencies): Update @typescript/native-preview to version 7.0.0-dev.20260319.1

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
2026-03-19 09:48:02 +01:00

155 lines
5.6 KiB
TypeScript

import z from "zod";
// --- JSearch API Request Types ---
export const searchParamsSchema = z.object({
query: z.string().min(1),
page: z.number().int().positive().optional(),
num_pages: z.number().int().positive().max(10).optional(),
date_posted: z.enum(["all", "today", "3days", "week", "month"]).optional(),
country: z
.string()
.length(2)
.regex(/^[A-Z]{2}$/)
.optional(),
remote_jobs_only: z.boolean().optional(),
employment_types: z.string().optional(),
job_requirements: z.string().optional(),
radius: z.number().positive().optional(),
exclude_job_publishers: z.string().optional(),
categories: z.string().optional(),
});
export type SearchParams = z.infer<typeof searchParamsSchema>;
// --- JSearch API Response Types ---
export const jobRequiredExperienceSchema = z.object({
no_experience_required: z.boolean().catch(false),
required_experience_in_months: z.number().nullable().catch(null),
experience_mentioned: z.boolean().catch(false),
experience_preferred: z.boolean().catch(false),
});
export const jobRequiredEducationSchema = z.object({
postgraduate_degree: z.boolean().catch(false),
professional_certification: z.boolean().catch(false),
high_school: z.boolean().catch(false),
associates_degree: z.boolean().catch(false),
bachelors_degree: z.boolean().catch(false),
degree_mentioned: z.boolean().catch(false),
degree_preferred: z.boolean().catch(false),
professional_certification_mentioned: z.boolean().catch(false),
});
export const applyOptionSchema = z.object({
publisher: z.string().catch(""),
apply_link: z.string().catch(""),
is_direct: z.boolean().catch(false),
});
export const jobResultSchema = z.object({
job_id: z.string(),
job_title: z.string(),
employer_name: z.string(),
employer_logo: z.string().nullable().catch(null),
employer_website: z.string().nullable().catch(null),
employer_company_type: z.string().nullable().catch(null),
employer_linkedin: z.string().nullable().catch(null),
job_publisher: z.string().catch(""),
job_employment_type: z.string().catch(""),
job_apply_link: z.string().catch(""),
job_apply_is_direct: z.boolean().catch(false),
job_apply_quality_score: z.number().nullable().catch(null),
job_description: z.string().catch(""),
job_is_remote: z.boolean().catch(false),
job_city: z.string().catch(""),
job_state: z.string().catch(""),
job_country: z.string().catch(""),
job_latitude: z.number().nullable().catch(null),
job_longitude: z.number().nullable().catch(null),
job_posted_at_timestamp: z.number().nullable().catch(null),
job_posted_at_datetime_utc: z.string().catch(""),
job_offer_expiration_datetime_utc: z.string().nullable().catch(null),
job_offer_expiration_timestamp: z.number().nullable().catch(null),
job_min_salary: z.number().nullable().catch(null),
job_max_salary: z.number().nullable().catch(null),
job_salary_currency: z.string().nullable().catch(null),
job_salary_period: z.string().nullable().catch(null),
job_benefits: z.array(z.string()).nullable().catch(null),
job_google_link: z.string().nullable().catch(null),
job_required_experience: jobRequiredExperienceSchema.catch({
no_experience_required: false,
required_experience_in_months: null,
experience_mentioned: false,
experience_preferred: false,
}),
job_required_skills: z.array(z.string()).nullable().catch(null),
job_required_education: jobRequiredEducationSchema.catch({
postgraduate_degree: false,
professional_certification: false,
high_school: false,
associates_degree: false,
bachelors_degree: false,
degree_mentioned: false,
degree_preferred: false,
professional_certification_mentioned: false,
}),
job_experience_in_place_of_education: z.boolean().nullable().catch(null),
job_highlights: z.record(z.string(), z.array(z.string())).nullable().catch(null),
job_posting_language: z.string().nullable().catch(null),
job_onet_soc: z.string().nullable().catch(null),
job_onet_job_zone: z.string().nullable().catch(null),
job_occupational_categories: z.array(z.string()).nullable().catch(null),
job_naics_code: z.string().nullable().catch(null),
job_naics_name: z.string().nullable().catch(null),
apply_options: z.array(applyOptionSchema).catch([]),
});
export type JobResult = z.infer<typeof jobResultSchema>;
export const searchResponseSchema = z.object({
status: z.string(),
request_id: z.string(),
parameters: z.record(z.string(), z.string()).catch({}),
data: z.array(jobResultSchema).catch([]),
});
export type SearchResponse = z.infer<typeof searchResponseSchema>;
export const jobDetailsResponseSchema = z.object({
status: z.string(),
request_id: z.string(),
data: z.array(jobResultSchema).catch([]),
});
export type JobDetailsResponse = z.infer<typeof jobDetailsResponseSchema>;
// --- Client-side Filter Types ---
export const postFilterOptionsSchema = z
.object({
minSalary: z.number().nonnegative().optional(),
maxSalary: z.number().nonnegative().optional(),
includeKeywords: z.array(z.string()).optional(),
excludeKeywords: z.array(z.string()).optional(),
excludeCompanies: z.array(z.string()).optional(),
directApplyOnly: z.boolean().optional(),
})
.refine((value) => value.minSalary == null || value.maxSalary == null || value.minSalary <= value.maxSalary, {
message: "minSalary must be less than or equal to maxSalary",
path: ["minSalary"],
});
export type PostFilterOptions = z.infer<typeof postFilterOptionsSchema>;
// --- Rate Limiting Types ---
export const rapidApiQuotaSchema = z.object({
limit: z.number(),
remaining: z.number(),
used: z.number(),
});
export type RapidApiQuota = z.infer<typeof rapidApiQuotaSchema>;