From 65952259ce847f16e8e4ac260f276111cef1b979 Mon Sep 17 00:00:00 2001 From: Devin Collins <3997333+ImDevinC@users.noreply.github.com> Date: Sat, 31 Jan 2026 08:48:17 -0800 Subject: [PATCH] feat: add remote jobs filter for JobSpy (#70) * feat(types): add jobspyIsRemote to TypeScript type definitions - Add jobspyIsRemote boolean fields to AppSettings interface - Follow three-field pattern: value, default, override - Update test fixture with default values (false) * feat(validation): add jobspyIsRemote validation schema Add Zod validation schema for jobspyIsRemote boolean setting to ensure type safety in the settings API endpoint. * feat(db): add jobspyIsRemote to database repository setting keys * feat(api): add jobspyIsRemote storage to settings API route * feat(service): add jobspyIsRemote to settings service with environment variable support * feat(jobspy): add isRemote parameter to JobSpy service interface * feat(pipeline): pass isRemote setting to JobSpy service * feat(python): add is_remote parameter to JobSpy scraper script * feat(ui): add Remote Jobs checkbox to JobSpy settings * feat(ui): add Remote badge to job display - Display Remote badge when job.isRemote === true - Position badge next to Source badge in JobHeader - Use Badge component with outline variant - Badge does not display when isRemote is false or null * docs(env): add JOBSPY_IS_REMOTE environment variable documentation - Added JobSpy section to .env.example with JOBSPY_IS_REMOTE variable - Documents remote-only job filtering option with default value of 0 (disabled) - Follows existing .env.example format with clear section headers and descriptions * test(remote-jobs): verify end-to-end functionality with comprehensive feedback loops --- .env.example | 6 ++++ extractors/jobspy/scrape_jobs.py | 7 +++-- .../src/client/components/JobHeader.tsx | 8 +++++ .../src/client/pages/SettingsPage.test.tsx | 3 ++ .../src/client/pages/SettingsPage.tsx | 11 +++++++ .../components/JobspySection.test.tsx | 2 ++ .../settings/components/JobspySection.tsx | 31 +++++++++++++++++++ .../src/client/pages/settings/types.ts | 1 + .../src/server/api/routes/settings.ts | 10 ++++++ .../src/server/pipeline/orchestrator.ts | 5 +++ .../src/server/repositories/settings.ts | 1 + orchestrator/src/server/services/jobspy.ts | 4 +++ orchestrator/src/server/services/settings.ts | 10 ++++++ orchestrator/src/shared/settings-schema.ts | 1 + orchestrator/src/shared/types.ts | 3 ++ 15 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 1c70c84..69d526d 100644 --- a/.env.example +++ b/.env.example @@ -27,3 +27,9 @@ BASIC_AUTH_PASSWORD= UKVISAJOBS_EMAIL= UKVISAJOBS_PASSWORD= UKVISAJOBS_HEADLESS=true + +# ============================================================================= +# JobSpy - Job search configuration +# ============================================================================= +# Filter for remote-only jobs (default: 0 = disabled) +# JOBSPY_IS_REMOTE=0 diff --git a/extractors/jobspy/scrape_jobs.py b/extractors/jobspy/scrape_jobs.py index 1bff190..7454f04 100644 --- a/extractors/jobspy/scrape_jobs.py +++ b/extractors/jobspy/scrape_jobs.py @@ -39,9 +39,12 @@ def main() -> int: hours_old = _env_int("JOBSPY_HOURS_OLD", 72) country_indeed = _env_str("JOBSPY_COUNTRY_INDEED", "UK") linkedin_fetch_description = _env_bool("JOBSPY_LINKEDIN_FETCH_DESCRIPTION", True) + is_remote = _env_bool("JOBSPY_IS_REMOTE", False) output_csv = Path(_env_str("JOBSPY_OUTPUT_CSV", "jobs.csv")) - output_json = Path(_env_str("JOBSPY_OUTPUT_JSON", str(output_csv.with_suffix(".json")))) + output_json = Path( + _env_str("JOBSPY_OUTPUT_JSON", str(output_csv.with_suffix(".json"))) + ) output_csv.parent.mkdir(parents=True, exist_ok=True) output_json.parent.mkdir(parents=True, exist_ok=True) @@ -55,6 +58,7 @@ def main() -> int: hours_old=hours_old, country_indeed=country_indeed, linkedin_fetch_description=linkedin_fetch_description, + is_remote=is_remote, ) print(f"Found {len(jobs)} jobs") @@ -75,4 +79,3 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) - diff --git a/orchestrator/src/client/components/JobHeader.tsx b/orchestrator/src/client/components/JobHeader.tsx index 2ee8ac8..a6d5104 100644 --- a/orchestrator/src/client/components/JobHeader.tsx +++ b/orchestrator/src/client/components/JobHeader.tsx @@ -206,6 +206,14 @@ export const JobHeader: React.FC = ({ > {sourceLabel[job.source]} + {job.isRemote === true && ( + + Remote + + )} {!isJobPage && (