Skip to content

Commit

Permalink
feat(getonboard): include junior roles + design ux
Browse files Browse the repository at this point in the history
  • Loading branch information
panquequelol committed Dec 27, 2024
1 parent e89ae89 commit 7d13f81
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 48 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"neverthrow": "^8.1.0",
"pino": "^9.5.0",
"playwright": "^1.39.0",
"ts-pattern": "^5.6.0",
"turndown": "^7.2.0",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const createConnection = Result.fromThrowable(() =>
export const OfferSchema = z.object({
id: z.string(),
title: z.string(),
company: z.string(),
company: z.union([z.string(), z.null()]),
content: z.string(),
url: z.string(),
type: z.union([
Expand Down
121 changes: 74 additions & 47 deletions src/sources/getonboard.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,110 @@
import { match, P } from "ts-pattern";
import { Offer, OfferSchema } from "../lib/db";
import { shorten } from "../lib/shorten";
import { fromAsyncThrowable } from "neverthrow";
import { unknown } from "zod";
import TurndownService from "turndown";

export async function getGetonboardEntryLevelOffers({
mode,
}: {
mode: "CHILE" | "REMOTE";
}): Promise<Offer[]> {
const { meta } = await fetchOffers({ mode });
const pages = [...Array(meta.total_pages + 1).keys()];
pages.shift(); // removes index 0
export async function getGetonboardEntryLevelOffers() {
const [programmingOffers, designOffers] = await Promise.all([
getEntryLevelJobsByCategory("programming"),
getEntryLevelJobsByCategory("design-ux"),
]);
return [...programmingOffers, ...designOffers];
}

async function getEntryLevelJobsByCategory(
category: "design-ux" | "programming"
): Promise<Offer[]> {
const { meta } = await fetchOffersByPage(1, category);
const totalPages = [...Array(meta.total_pages + 1).keys()];
totalPages.shift(); // removes index 0

const offerPromises = pages.map(async (page) => {
const jobs = await fetchOffers({ page, mode });
// modality 4 = internship, seniortiy 1 = no experience required
const everyEntryLevelOFferByPagePromise = totalPages.map(async (page) => {
const jobs = await fetchOffersByPage(page, category);
// modality 4 intern
// seniority 1 no experience
// seniority 2 junior
const entryLevel = jobs.data
.filter(
(job: any) =>
job.attributes.modality.data.id === 4 ||
job.attributes.seniority.data.id === 1
(job.attributes.modality.data.id === 4 ||
job.attributes.seniority.data.id === 1 ||
job.attributes.seniority.data.id === 2) &&
(job.attributes.countries.includes("Chile") ||
job.attributes.remote_modality === "fully_remote")
)
.map(async (job: any) => {
const companyId = job.attributes.company.data.id;
const res = await fetch(
"https://www.getonbrd.com/api/v0/companies/" + companyId
const companyName = await fromAsyncThrowable(getCompanyName)(
job.attributes.company.data.id
);
const data = await res.json();
return { ...job, fetched_company_name: data.data.attributes.name };
if (companyName.isErr()) return { ...job, fetched_company_name: null };
return { ...job, fetched_company_name: companyName.value };
});
return Promise.all(entryLevel);
});
const formattedOffers = (await Promise.all(offerPromises)).flat(Infinity);
const entryLevelOffers = formattedOffers.map((job) => {
// design offers hav a different url
// job.data.attributes.category_name == "Programming" means /jobs/programacion/
const url = "https://www.getonbrd.cl/jobs/programacion/" + job.id;
const entryLevelOffers = (
await Promise.all(everyEntryLevelOFferByPagePromise)
).flat(Infinity);
const validFormattedOffers = entryLevelOffers.map((job) => {
const url =
(job.attributes.category_name === "Programming"
? "https://www.getonbrd.com/jobs/programming/"
: "https://www.getonbrd.com/jobs/design-ux/") + job.id;
let location: string | null = null;
if (job.attributes.remote_modality == "fully_remote") {
location = "REMOTE";
} else if (job.attributes.countries == "Chile") {
location = "ONSITE";
}
const turndownService = new TurndownService();
const markdownContent = turndownService.turndown(
job.attributes.description
);
const offer = OfferSchema.safeParse({
id: job.id,
date: new Date(job.attributes.published_at * 1000),
content: shorten(job.attributes.description, 128),
content: shorten(markdownContent, 128),
source: "GETONBOARD",
title: job.attributes.title,
company: job.fetched_company_name,
url,
type: job.attributes.modality.data.id ? "INTERNSHIP" : "NEWGRAD",
...(job.attributes.min_salary || job.attributes.max_salary
? {
salary:
job.attributes.min_salary === job.attributes.max_salary
? `$${job.attributes.min_salary} USD`
: `Entre $${job.attributes.min_salary} y $${job.attributes.max_salary} USD`,
}
: {}),
type: match<{ modality: number; seniority: number }>({
modality: job.attributes.modality.data.id,
seniority: job.attributes.seniority.data.id,
})
.with({ modality: 4 }, () => "INTERNSHIP")
.with({ seniority: 1 }, () => "NEWGRAD")
.with({ seniority: 2 }, () => "JUNIOR")
.otherwise(() => unknown),
location:
job.attributes.remote_modality == "fully_remote" ? "REMOTE" : "ONSITE",
});
return offer.success ? offer.data : null;
});
return entryLevelOffers.filter((offer) => offer !== null);
return validFormattedOffers.filter((offer) => offer !== null);
}

async function fetchOffers(args: { page?: number; mode: "CHILE" | "REMOTE" }) {
const queryParams = new URLSearchParams();

if (args.page) queryParams.append("page", args.page.toString());

if (args.mode === "REMOTE") {
queryParams.append("remote", "true");
} else {
queryParams.append("country_code", "CL");
}

const url = `https://www.getonbrd.com/api/v0/categories/programming/jobs?${queryParams}`;
const res = await fetch(url);
async function fetchOffersByPage(
page: number = 1,
category: "programming" | "design-ux" = "programming"
) {
const res = await fetch(
`https://www.getonbrd.com/api/v0/categories/${category}/jobs?remote=true&page=${page}`
);
return res.json();
}

(async () => {
// TODO: merge this 2, can have repeated offers but ids are unique, filter them.
// TODO: implement design offers
const offers1 = await getGetonboardEntryLevelOffers({ mode: "CHILE" });
const offers2 = await getGetonboardEntryLevelOffers({ mode: "REMOTE" });
console.log(offers1);
console.log(offers2);
})();
async function getCompanyName(id: string) {
const res = await fetch("https://www.getonbrd.com/api/v0/companies/" + id);
const data = await res.json();
return data.data.attributes.name;
}

0 comments on commit 7d13f81

Please sign in to comment.