mirror of
https://github.com/prdlk/jobyssey.git
synced 2026-11-02 13:12:49 +00:00
117 lines
4.0 KiB
TypeScript
Executable File
117 lines
4.0 KiB
TypeScript
Executable File
import { Database } from "bun:sqlite"
|
|
import { existsSync } from "node:fs"
|
|
|
|
export const DB_PATH = process.env.JOBSDB_PATH ?? "documents/postings/jobs.db"
|
|
|
|
export const REMOTE_CLASS: Record<string, string> = { remote: "R1", "remote-global": "R1", "remote-regional": "R2", hybrid: "H", onsite: "O" }
|
|
export const CLASS_LABEL: Record<string, string> = { R1: "Remote (global)", R2: "Remote (regional)", H: "Hybrid", O: "Onsite" }
|
|
|
|
export interface Row {
|
|
posting_id: string
|
|
company_id: string | null
|
|
company_name: string | null
|
|
title: string | null
|
|
seniority: string | null
|
|
posted_date: string | null
|
|
location_text: string | null
|
|
location_class: string | null
|
|
location_country: string | null
|
|
salary_min: string | null
|
|
salary_max: string | null
|
|
salary_currency: string | null
|
|
url: string | null
|
|
source_ats: string | null
|
|
scrape_timestamp: string | null
|
|
target_sector: string | null
|
|
crypto_native: string | null
|
|
priority_tier: number | null
|
|
}
|
|
|
|
export interface Job {
|
|
id: string
|
|
title: string | null
|
|
company: string | null
|
|
location: string | null
|
|
date: string | null
|
|
date_approximate: boolean
|
|
url: string | null
|
|
description: null
|
|
remote: string | null
|
|
seniority: string | null
|
|
salary: string | null
|
|
sector: string | null
|
|
crypto_native: boolean
|
|
priority_tier: number | null
|
|
ats: string | null
|
|
snapshot: string | null
|
|
}
|
|
|
|
export function openDb(): Database {
|
|
if (!existsSync(DB_PATH)) {
|
|
writeError(`database not found at ${DB_PATH} (set JOBSDB_PATH)`, "NO_DB")
|
|
process.exit(1)
|
|
}
|
|
return new Database(DB_PATH, { readonly: true })
|
|
}
|
|
|
|
/** "Posted 8 Days Ago" + scrape_timestamp -> ISO date. "30+" is a floor, flagged approximate. */
|
|
export function resolveDate(posted: string | null, scraped: string | null): { date: string | null; approximate: boolean } {
|
|
if (!posted) return { date: null, approximate: false }
|
|
// greenhouse/ashby/lever store ISO timestamps; workday stores relative text.
|
|
if (/^\d{4}-\d{2}-\d{2}/.test(posted)) return { date: posted.slice(0, 10), approximate: false }
|
|
if (!scraped) return { date: null, approximate: false }
|
|
const base = new Date(scraped)
|
|
if (isNaN(base.getTime())) return { date: null, approximate: false }
|
|
const m = /(\d+)(\+?)\s*Days?/i.exec(posted)
|
|
let days: number
|
|
let approximate = false
|
|
if (/Today/i.test(posted)) days = 0
|
|
else if (/Yesterday/i.test(posted)) days = 1
|
|
else if (m) { days = Number(m[1]); approximate = m[2] === "+" }
|
|
else return { date: null, approximate: false }
|
|
base.setUTCDate(base.getUTCDate() - days)
|
|
return { date: base.toISOString().slice(0, 10), approximate }
|
|
}
|
|
|
|
export function toJob(r: Row): Job {
|
|
const { date, approximate } = resolveDate(r.posted_date, r.scrape_timestamp)
|
|
const salary = r.salary_min && r.salary_max ? `${r.salary_min}-${r.salary_max} ${r.salary_currency ?? ""}`.trim() : null
|
|
return {
|
|
id: r.posting_id,
|
|
title: r.title || null,
|
|
company: r.company_name || null,
|
|
location: r.location_text || null,
|
|
date,
|
|
date_approximate: approximate,
|
|
url: r.url || null,
|
|
description: null,
|
|
remote: r.location_class ? CLASS_LABEL[r.location_class] ?? null : null,
|
|
seniority: r.seniority || null,
|
|
salary,
|
|
sector: r.target_sector || null,
|
|
crypto_native: r.crypto_native === "True",
|
|
priority_tier: r.priority_tier ?? null,
|
|
ats: r.source_ats || null,
|
|
snapshot: r.scrape_timestamp ? r.scrape_timestamp.slice(0, 10) : null,
|
|
}
|
|
}
|
|
|
|
export function writeError(error: string, code: string): void {
|
|
process.stderr.write(JSON.stringify({ error, code }) + "\n")
|
|
}
|
|
|
|
export function renderTable(jobs: Job[]): string {
|
|
if (jobs.length === 0) return "No results."
|
|
const rows = jobs.map((j) =>
|
|
[
|
|
(j.title ?? "").slice(0, 44).padEnd(44),
|
|
(j.company ?? "-").slice(0, 22).padEnd(22),
|
|
(j.location ?? "-").slice(0, 22).padEnd(22),
|
|
(j.remote ?? "-").slice(0, 16).padEnd(16),
|
|
j.date ? j.date + (j.date_approximate ? "~" : " ") : "-".padEnd(11),
|
|
j.url ?? "",
|
|
].join(" "),
|
|
)
|
|
return rows.join("\n")
|
|
}
|