@profullstack/agenticjobs 0.2.0 → 0.4.0
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/README.md +20 -0
- package/dist/cli/index.d.ts +9 -1
- package/dist/cli/index.js +99 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/client/client.d.ts +16 -0
- package/dist/client/client.js +8 -0
- package/dist/client/client.js.map +1 -1
- package/dist/config.d.ts +12 -2
- package/dist/config.js +44 -4
- package/dist/config.js.map +1 -1
- package/dist/core/applications.js +4 -1
- package/dist/core/applications.js.map +1 -1
- package/dist/core/auth.d.ts +2 -0
- package/dist/core/auth.js +2 -1
- package/dist/core/auth.js.map +1 -1
- package/dist/core/candidates.d.ts +42 -0
- package/dist/core/candidates.js +143 -0
- package/dist/core/candidates.js.map +1 -0
- package/dist/core/import-job.d.ts +36 -0
- package/dist/core/import-job.js +221 -0
- package/dist/core/import-job.js.map +1 -0
- package/dist/core/jobs.d.ts +29 -0
- package/dist/core/jobs.js +101 -31
- package/dist/core/jobs.js.map +1 -1
- package/dist/core/mail.d.ts +64 -0
- package/dist/core/mail.js +125 -0
- package/dist/core/mail.js.map +1 -0
- package/dist/core/resumes.d.ts +23 -1
- package/dist/core/resumes.js +86 -9
- package/dist/core/resumes.js.map +1 -1
- package/dist/directory/fetch.d.ts +11 -1
- package/dist/directory/fetch.js +23 -8
- package/dist/directory/fetch.js.map +1 -1
- package/dist/directory/registry.d.ts +1 -0
- package/dist/directory/registry.js +17 -1
- package/dist/directory/registry.js.map +1 -1
- package/dist/markup/resume.js +7 -1
- package/dist/markup/resume.js.map +1 -1
- package/dist/schema/job.d.ts +13 -10
- package/dist/schema/job.js.map +1 -1
- package/dist/schema/query.js +8 -4
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/text.d.ts +12 -1
- package/dist/schema/text.js +14 -3
- package/dist/schema/text.js.map +1 -1
- package/dist/server/app.d.ts +6 -1
- package/dist/server/app.js +7 -2
- package/dist/server/app.js.map +1 -1
- package/dist/server/deps.d.ts +3 -0
- package/dist/server/routes/api.js +201 -24
- package/dist/server/routes/api.js.map +1 -1
- package/dist/server/routes/discovery.js +162 -1
- package/dist/server/routes/discovery.js.map +1 -1
- package/dist/server/routes/pages.js +44 -5
- package/dist/server/routes/pages.js.map +1 -1
- package/dist/server/serve.js +22 -7
- package/dist/server/serve.js.map +1 -1
- package/dist/views/auth.d.ts +1 -1
- package/dist/views/auth.js +3 -1
- package/dist/views/auth.js.map +1 -1
- package/dist/views/candidates.d.ts +47 -0
- package/dist/views/candidates.js +21 -0
- package/dist/views/candidates.js.map +1 -0
- package/dist/views/jobs.d.ts +13 -0
- package/dist/views/jobs.js +35 -10
- package/dist/views/jobs.js.map +1 -1
- package/dist/views/layout.js +2 -2
- package/dist/views/layout.js.map +1 -1
- package/dist/views/me.js +3 -1
- package/dist/views/me.js.map +1 -1
- package/dist/views/post.js +1 -1
- package/dist/views/post.js.map +1 -1
- package/docs/openjob.md +16 -9
- package/migrations/0006_board_only_applications.sql +34 -0
- package/migrations/0007_public_resumes.sql +24 -0
- package/migrations/0008_backfill_public_slugs.sql +57 -0
- package/migrations/0009_shorten_runaway_slugs.sql +21 -0
- package/package.json +16 -17
- package/web/public/app.css +45 -2
- package/web/public/sw.js +29 -5
package/dist/core/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAe1C,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAChD,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A resume, reduced to what a directory row needs.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is read out of the parsed resume rather than asked for
|
|
5
|
+
* again. A candidate should not have to fill in a profile that restates the
|
|
6
|
+
* document they already wrote, and a second copy of the same facts is a second
|
|
7
|
+
* copy to keep in step.
|
|
8
|
+
*/
|
|
9
|
+
import type { Resume } from './resumes.ts';
|
|
10
|
+
import type { CandidateSummary } from '../views/candidates.tsx';
|
|
11
|
+
/**
|
|
12
|
+
* The name shown in the directory.
|
|
13
|
+
*
|
|
14
|
+
* A resume with no usable h1 falls back to its title, which its owner wrote
|
|
15
|
+
* and which is at least theirs. It never falls back to an email address:
|
|
16
|
+
* publishing a resume should not mean publishing an address as the headline.
|
|
17
|
+
*/
|
|
18
|
+
export declare function nameOf(resume: Resume): string;
|
|
19
|
+
export declare function toCandidateSummary(resume: Resume): CandidateSummary;
|
|
20
|
+
/**
|
|
21
|
+
* Read `tags=javascript,react,node.js` into a list.
|
|
22
|
+
*
|
|
23
|
+
* `skill=` is accepted as a one-tag alias, because that is what the first
|
|
24
|
+
* version of the badge links used and those URLs are already out there.
|
|
25
|
+
*/
|
|
26
|
+
export declare function tagsFrom(params: URLSearchParams): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The candidates who list ALL of these tags.
|
|
29
|
+
*
|
|
30
|
+
* Narrowing rather than widening: someone asking for javascript, react and
|
|
31
|
+
* node.js together is describing one person's skill set, not three separate
|
|
32
|
+
* searches. A single tag behaves the same either way.
|
|
33
|
+
*
|
|
34
|
+
* Matched on the whole tag rather than as a substring, because that is what
|
|
35
|
+
* the badge links to: "Go" must not match "MongoDB".
|
|
36
|
+
*/
|
|
37
|
+
export declare function withTags(candidates: CandidateSummary[], tags: string[]): CandidateSummary[];
|
|
38
|
+
/** Every tag anybody lists, most common first, for a browsable index. */
|
|
39
|
+
export declare function allTags(candidates: CandidateSummary[]): {
|
|
40
|
+
tag: string;
|
|
41
|
+
count: number;
|
|
42
|
+
}[];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A resume, reduced to what a directory row needs.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is read out of the parsed resume rather than asked for
|
|
5
|
+
* again. A candidate should not have to fill in a profile that restates the
|
|
6
|
+
* document they already wrote, and a second copy of the same facts is a second
|
|
7
|
+
* copy to keep in step.
|
|
8
|
+
*/
|
|
9
|
+
/** Contact keys that read as a place rather than an address. */
|
|
10
|
+
const LOCATION_KEYS = /^(location|based|city|where|region)$/i;
|
|
11
|
+
/**
|
|
12
|
+
* Skills, from the section a resume marks as skills.
|
|
13
|
+
*
|
|
14
|
+
* Takes the bullets under the heading, and splits a comma-separated line,
|
|
15
|
+
* because both are how people write that section. Capped, because a directory
|
|
16
|
+
* row is a summary and some resumes list sixty.
|
|
17
|
+
*/
|
|
18
|
+
function skillsOf(resume, limit = 8) {
|
|
19
|
+
const section = resume.parsed?.sections.find((item) => item.kind === 'skills');
|
|
20
|
+
if (section === undefined)
|
|
21
|
+
return [];
|
|
22
|
+
const fromBullets = section.markdown
|
|
23
|
+
.split('\n')
|
|
24
|
+
.map((line) => line.replace(/^\s*[-*]\s+/, '').trim())
|
|
25
|
+
// A skills bullet is very often "**Languages:** JavaScript, Go", and the
|
|
26
|
+
// label is a category rather than a skill. Without dropping it the first
|
|
27
|
+
// badge on the card reads "**Languages:** JavaScript".
|
|
28
|
+
.map((line) => line.replace(/^\*{0,2}[^*:]{1,40}:\*{0,2}\s*/, ''))
|
|
29
|
+
.filter((line) => line !== '' && !line.startsWith('#'));
|
|
30
|
+
const flattened = fromBullets.flatMap((line) => line.includes(',') ? line.split(',').map((part) => part.trim()) : [line]);
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const skill of flattened) {
|
|
34
|
+
// A sentence is prose that happened to be in the skills section, not a
|
|
35
|
+
// skill, and a badge is the wrong shape for it.
|
|
36
|
+
const cleaned = skill.replace(/\*\*/g, '').replace(/^`|`$/g, '').trim();
|
|
37
|
+
if (cleaned === '' || cleaned.length > 40)
|
|
38
|
+
continue;
|
|
39
|
+
const key = cleaned.toLowerCase();
|
|
40
|
+
if (seen.has(key))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(key);
|
|
43
|
+
out.push(cleaned);
|
|
44
|
+
if (out.length >= limit)
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function locationOf(resume) {
|
|
50
|
+
const found = resume.parsed?.contact.find((item) => LOCATION_KEYS.test(item.key));
|
|
51
|
+
return found?.value ?? null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The longest a person's name is allowed to be before it is not one.
|
|
55
|
+
*
|
|
56
|
+
* A resume whose Markdown lost its line breaks parses as a single h1 holding
|
|
57
|
+
* the entire document, and the "name" then comes back as three thousand
|
|
58
|
+
* characters of resume. That happened, and it produced a candidate card
|
|
59
|
+
* captioned with a whole CV and a URL to match, so the length is checked
|
|
60
|
+
* rather than assumed.
|
|
61
|
+
*/
|
|
62
|
+
const NAME_MAX = 80;
|
|
63
|
+
/**
|
|
64
|
+
* The name shown in the directory.
|
|
65
|
+
*
|
|
66
|
+
* A resume with no usable h1 falls back to its title, which its owner wrote
|
|
67
|
+
* and which is at least theirs. It never falls back to an email address:
|
|
68
|
+
* publishing a resume should not mean publishing an address as the headline.
|
|
69
|
+
*/
|
|
70
|
+
export function nameOf(resume) {
|
|
71
|
+
const parsed = resume.parsed?.name?.trim();
|
|
72
|
+
if (parsed !== undefined && parsed !== '' && parsed.length <= NAME_MAX)
|
|
73
|
+
return parsed;
|
|
74
|
+
const title = resume.title.trim();
|
|
75
|
+
return title === '' || title.length > NAME_MAX ? 'Candidate' : title;
|
|
76
|
+
}
|
|
77
|
+
export function toCandidateSummary(resume) {
|
|
78
|
+
return {
|
|
79
|
+
slug: resume.publicSlug ?? '',
|
|
80
|
+
name: nameOf(resume),
|
|
81
|
+
headline: resume.parsed?.headline ?? null,
|
|
82
|
+
location: locationOf(resume),
|
|
83
|
+
skills: skillsOf(resume),
|
|
84
|
+
updatedAt: resume.updatedAt,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read `tags=javascript,react,node.js` into a list.
|
|
89
|
+
*
|
|
90
|
+
* `skill=` is accepted as a one-tag alias, because that is what the first
|
|
91
|
+
* version of the badge links used and those URLs are already out there.
|
|
92
|
+
*/
|
|
93
|
+
export function tagsFrom(params) {
|
|
94
|
+
const raw = params.get('tags') ?? params.get('skill') ?? '';
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const part of raw.split(',')) {
|
|
98
|
+
const tag = part.trim();
|
|
99
|
+
if (tag === '')
|
|
100
|
+
continue;
|
|
101
|
+
const key = tag.toLowerCase();
|
|
102
|
+
if (seen.has(key))
|
|
103
|
+
continue;
|
|
104
|
+
seen.add(key);
|
|
105
|
+
out.push(tag);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The candidates who list ALL of these tags.
|
|
111
|
+
*
|
|
112
|
+
* Narrowing rather than widening: someone asking for javascript, react and
|
|
113
|
+
* node.js together is describing one person's skill set, not three separate
|
|
114
|
+
* searches. A single tag behaves the same either way.
|
|
115
|
+
*
|
|
116
|
+
* Matched on the whole tag rather than as a substring, because that is what
|
|
117
|
+
* the badge links to: "Go" must not match "MongoDB".
|
|
118
|
+
*/
|
|
119
|
+
export function withTags(candidates, tags) {
|
|
120
|
+
if (tags.length === 0)
|
|
121
|
+
return candidates;
|
|
122
|
+
const wanted = tags.map((tag) => tag.toLowerCase());
|
|
123
|
+
return candidates.filter((candidate) => {
|
|
124
|
+
const has = new Set(candidate.skills.map((skill) => skill.toLowerCase()));
|
|
125
|
+
return wanted.every((tag) => has.has(tag));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** Every tag anybody lists, most common first, for a browsable index. */
|
|
129
|
+
export function allTags(candidates) {
|
|
130
|
+
const counts = new Map();
|
|
131
|
+
for (const candidate of candidates) {
|
|
132
|
+
for (const skill of candidate.skills) {
|
|
133
|
+
const key = skill.toLowerCase();
|
|
134
|
+
const seen = counts.get(key);
|
|
135
|
+
if (seen === undefined)
|
|
136
|
+
counts.set(key, { tag: skill, count: 1 });
|
|
137
|
+
else
|
|
138
|
+
seen.count += 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [...counts.values()].sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=candidates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/core/candidates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a job posting somewhere else into a listing here.
|
|
3
|
+
*
|
|
4
|
+
* This is what a URL is for now: applications are taken on the board, so a
|
|
5
|
+
* link to a job elsewhere is a source to import, never a destination to send
|
|
6
|
+
* an applicant to.
|
|
7
|
+
*
|
|
8
|
+
* Two paths, in this order:
|
|
9
|
+
*
|
|
10
|
+
* 1. **schema.org JobPosting in JSON-LD.** Most boards emit it, Google asks
|
|
11
|
+
* for it, and it is structured, so it is worth trying first and worth
|
|
12
|
+
* trusting when it is there.
|
|
13
|
+
* 2. **The page itself.** og:title and the readable text. Everything a page
|
|
14
|
+
* does not say stays unset rather than being guessed at.
|
|
15
|
+
*
|
|
16
|
+
* An import is always a DRAFT. Extraction from a page nobody designed for it
|
|
17
|
+
* is approximate, and this board's whole seam is that a machine prepares and a
|
|
18
|
+
* person releases. A silently published import would be a scraped listing on a
|
|
19
|
+
* board whose pitch is that nothing here is scraped.
|
|
20
|
+
*/
|
|
21
|
+
import { type EmploymentType, type Workplace } from '../schema/job.ts';
|
|
22
|
+
export interface ImportedJob {
|
|
23
|
+
title: string;
|
|
24
|
+
description: string;
|
|
25
|
+
sourceUrl: string;
|
|
26
|
+
employmentType?: EmploymentType;
|
|
27
|
+
workplace?: Workplace;
|
|
28
|
+
location?: string;
|
|
29
|
+
/** Which path produced this, so the caller can say so rather than imply more. */
|
|
30
|
+
via: 'jsonld' | 'page';
|
|
31
|
+
/** What could not be read, in words a person can act on. */
|
|
32
|
+
warnings: string[];
|
|
33
|
+
}
|
|
34
|
+
export declare class JobImportProblem extends Error {
|
|
35
|
+
}
|
|
36
|
+
export declare function extractJob(html: string, sourceUrl: string): ImportedJob;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a job posting somewhere else into a listing here.
|
|
3
|
+
*
|
|
4
|
+
* This is what a URL is for now: applications are taken on the board, so a
|
|
5
|
+
* link to a job elsewhere is a source to import, never a destination to send
|
|
6
|
+
* an applicant to.
|
|
7
|
+
*
|
|
8
|
+
* Two paths, in this order:
|
|
9
|
+
*
|
|
10
|
+
* 1. **schema.org JobPosting in JSON-LD.** Most boards emit it, Google asks
|
|
11
|
+
* for it, and it is structured, so it is worth trying first and worth
|
|
12
|
+
* trusting when it is there.
|
|
13
|
+
* 2. **The page itself.** og:title and the readable text. Everything a page
|
|
14
|
+
* does not say stays unset rather than being guessed at.
|
|
15
|
+
*
|
|
16
|
+
* An import is always a DRAFT. Extraction from a page nobody designed for it
|
|
17
|
+
* is approximate, and this board's whole seam is that a machine prepares and a
|
|
18
|
+
* person releases. A silently published import would be a scraped listing on a
|
|
19
|
+
* board whose pitch is that nothing here is scraped.
|
|
20
|
+
*/
|
|
21
|
+
import { EMPLOYMENT_TYPES, WORKPLACES } from "../schema/job.js";
|
|
22
|
+
export class JobImportProblem extends Error {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Elements whose contents are never the job.
|
|
26
|
+
*
|
|
27
|
+
* The first group carries no readable text at all. The second is site
|
|
28
|
+
* furniture: a page without a <main> falls back to the whole <body>, and
|
|
29
|
+
* without this the listing ends up carrying "Back to gigs", the view counter,
|
|
30
|
+
* a testimonials block and an advertisement. That is what happened on the
|
|
31
|
+
* first real import, so this is not hypothetical tidying.
|
|
32
|
+
*/
|
|
33
|
+
const DEAD = /<(script|style|template|noscript|svg|nav|header|footer|aside|form|button|select)[^>]*>[\s\S]*?<\/\1>/gi;
|
|
34
|
+
function stripTags(html) {
|
|
35
|
+
return html
|
|
36
|
+
.replace(DEAD, ' ')
|
|
37
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
38
|
+
.replace(/<\/(p|div|li|h[1-6]|tr)>/gi, '\n')
|
|
39
|
+
.replace(/<li[^>]*>/gi, '- ')
|
|
40
|
+
.replace(/<[^>]+>/g, ' ');
|
|
41
|
+
}
|
|
42
|
+
/** The five named entities plus numeric ones; enough for text nobody will re-parse. */
|
|
43
|
+
function decode(text) {
|
|
44
|
+
return text
|
|
45
|
+
.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, body) => {
|
|
46
|
+
const named = {
|
|
47
|
+
amp: '&',
|
|
48
|
+
lt: '<',
|
|
49
|
+
gt: '>',
|
|
50
|
+
quot: '"',
|
|
51
|
+
apos: "'",
|
|
52
|
+
nbsp: ' ',
|
|
53
|
+
'#39': "'",
|
|
54
|
+
};
|
|
55
|
+
const key = body.toLowerCase();
|
|
56
|
+
if (named[key] !== undefined)
|
|
57
|
+
return named[key];
|
|
58
|
+
if (body.startsWith('#x') || body.startsWith('#X')) {
|
|
59
|
+
const code = Number.parseInt(body.slice(2), 16);
|
|
60
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
|
|
61
|
+
}
|
|
62
|
+
if (body.startsWith('#')) {
|
|
63
|
+
const code = Number.parseInt(body.slice(1), 10);
|
|
64
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
|
|
65
|
+
}
|
|
66
|
+
return whole;
|
|
67
|
+
})
|
|
68
|
+
.replace(/ /g, ' ');
|
|
69
|
+
}
|
|
70
|
+
function tidy(text) {
|
|
71
|
+
return decode(text)
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((line) => line.replace(/[ \t]+/g, ' ').trim())
|
|
74
|
+
.filter((line, index, lines) => line !== '' || lines[index - 1] !== '')
|
|
75
|
+
.join('\n')
|
|
76
|
+
.trim();
|
|
77
|
+
}
|
|
78
|
+
function metaContent(html, key) {
|
|
79
|
+
// Attribute order varies, so both orders are tried rather than assumed.
|
|
80
|
+
const patterns = [
|
|
81
|
+
new RegExp(`<meta[^>]+(?:property|name)=["']${key}["'][^>]+content=["']([^"']*)["']`, 'i'),
|
|
82
|
+
new RegExp(`<meta[^>]+content=["']([^"']*)["'][^>]+(?:property|name)=["']${key}["']`, 'i'),
|
|
83
|
+
];
|
|
84
|
+
for (const pattern of patterns) {
|
|
85
|
+
const found = pattern.exec(html)?.[1];
|
|
86
|
+
if (found !== undefined && found.trim() !== '')
|
|
87
|
+
return decode(found.trim());
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/** JSON-LD blocks, including the @graph wrapper a lot of CMSes emit. */
|
|
92
|
+
function jsonLdNodes(html) {
|
|
93
|
+
const nodes = [];
|
|
94
|
+
const blocks = html.matchAll(/<script[^>]+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi);
|
|
95
|
+
for (const block of blocks) {
|
|
96
|
+
let parsed;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(decode(block[1] ?? ''));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// A malformed block is not a reason to abandon the import; there is
|
|
102
|
+
// usually more than one and the page still has readable text.
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const queue = Array.isArray(parsed) ? [...parsed] : [parsed];
|
|
106
|
+
while (queue.length > 0) {
|
|
107
|
+
const node = queue.shift();
|
|
108
|
+
if (typeof node !== 'object' || node === null)
|
|
109
|
+
continue;
|
|
110
|
+
const record = node;
|
|
111
|
+
const graph = record['@graph'];
|
|
112
|
+
if (Array.isArray(graph))
|
|
113
|
+
queue.push(...graph);
|
|
114
|
+
nodes.push(record);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return nodes;
|
|
118
|
+
}
|
|
119
|
+
function typeOf(node) {
|
|
120
|
+
const raw = node['@type'];
|
|
121
|
+
if (typeof raw === 'string')
|
|
122
|
+
return [raw];
|
|
123
|
+
if (Array.isArray(raw))
|
|
124
|
+
return raw.filter((t) => typeof t === 'string');
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
/** schema.org spells it MONTHLY, PART_TIME and so on. */
|
|
128
|
+
function employmentTypeOf(value) {
|
|
129
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
130
|
+
if (typeof raw !== 'string')
|
|
131
|
+
return undefined;
|
|
132
|
+
const normalised = raw.toLowerCase().replace(/_/g, '-').trim();
|
|
133
|
+
return EMPLOYMENT_TYPES.includes(normalised)
|
|
134
|
+
? normalised
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
137
|
+
function workplaceOf(node) {
|
|
138
|
+
const remote = node['jobLocationType'];
|
|
139
|
+
if (typeof remote === 'string' && remote.toUpperCase().includes('TELECOMMUTE'))
|
|
140
|
+
return 'remote';
|
|
141
|
+
const raw = node['workplace'];
|
|
142
|
+
if (typeof raw === 'string' && WORKPLACES.includes(raw.toLowerCase())) {
|
|
143
|
+
return raw.toLowerCase();
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
function locationOf(node) {
|
|
148
|
+
const place = Array.isArray(node['jobLocation']) ? node['jobLocation'][0] : node['jobLocation'];
|
|
149
|
+
if (typeof place !== 'object' || place === null)
|
|
150
|
+
return undefined;
|
|
151
|
+
const address = place['address'];
|
|
152
|
+
if (typeof address === 'string')
|
|
153
|
+
return address;
|
|
154
|
+
if (typeof address !== 'object' || address === null)
|
|
155
|
+
return undefined;
|
|
156
|
+
const parts = ['addressLocality', 'addressRegion', 'addressCountry']
|
|
157
|
+
.map((key) => address[key])
|
|
158
|
+
.filter((part) => typeof part === 'string' && part.trim() !== '');
|
|
159
|
+
return parts.length === 0 ? undefined : parts.join(', ');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The readable body of a page, without the furniture.
|
|
163
|
+
*
|
|
164
|
+
* Not a reader-mode implementation: it takes `<main>` or `<article>` when the
|
|
165
|
+
* page marks one, because a page that says where its content is should be
|
|
166
|
+
* believed, and otherwise the whole body. The result is a person's starting
|
|
167
|
+
* point, which is why the import is a draft.
|
|
168
|
+
*/
|
|
169
|
+
function readableText(html) {
|
|
170
|
+
const main = /<main[^>]*>([\s\S]*?)<\/main>/i.exec(html)?.[1] ??
|
|
171
|
+
/<article[^>]*>([\s\S]*?)<\/article>/i.exec(html)?.[1] ??
|
|
172
|
+
/<body[^>]*>([\s\S]*?)<\/body>/i.exec(html)?.[1] ??
|
|
173
|
+
html;
|
|
174
|
+
return tidy(stripTags(main));
|
|
175
|
+
}
|
|
176
|
+
/** A page title usually carries the site name; the listing should not. */
|
|
177
|
+
function trimSiteName(title) {
|
|
178
|
+
return title.split(/\s+[|·—–]\s+/)[0]?.trim() || title.trim();
|
|
179
|
+
}
|
|
180
|
+
export function extractJob(html, sourceUrl) {
|
|
181
|
+
const warnings = [];
|
|
182
|
+
const posting = jsonLdNodes(html).find((node) => typeOf(node).includes('JobPosting'));
|
|
183
|
+
if (posting !== undefined) {
|
|
184
|
+
const title = typeof posting['title'] === 'string' ? posting['title'].trim() : '';
|
|
185
|
+
const rawDescription = typeof posting['description'] === 'string' ? posting['description'] : '';
|
|
186
|
+
const description = tidy(stripTags(rawDescription));
|
|
187
|
+
if (title !== '' && description !== '') {
|
|
188
|
+
const employmentType = employmentTypeOf(posting['employmentType']);
|
|
189
|
+
const workplace = workplaceOf(posting);
|
|
190
|
+
const location = locationOf(posting);
|
|
191
|
+
if (employmentType === undefined)
|
|
192
|
+
warnings.push('No employment type in the posting.');
|
|
193
|
+
if (workplace === undefined)
|
|
194
|
+
warnings.push('No workplace, so it was left unset.');
|
|
195
|
+
return {
|
|
196
|
+
title,
|
|
197
|
+
description,
|
|
198
|
+
sourceUrl,
|
|
199
|
+
via: 'jsonld',
|
|
200
|
+
warnings,
|
|
201
|
+
...(employmentType === undefined ? {} : { employmentType }),
|
|
202
|
+
...(workplace === undefined ? {} : { workplace }),
|
|
203
|
+
...(location === undefined ? {} : { location }),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
warnings.push('The page had a JobPosting but it was missing a title or a description.');
|
|
207
|
+
}
|
|
208
|
+
const title = trimSiteName(metaContent(html, 'og:title') ??
|
|
209
|
+
decode(/<h1[^>]*>([\s\S]*?)<\/h1>/i.exec(html)?.[1]?.replace(/<[^>]+>/g, '') ?? '') ??
|
|
210
|
+
decode(/<title[^>]*>([\s\S]*?)<\/title>/i.exec(html)?.[1] ?? ''));
|
|
211
|
+
if (title === '')
|
|
212
|
+
throw new JobImportProblem('That page has no title, so there is nothing to import.');
|
|
213
|
+
const body = readableText(html);
|
|
214
|
+
const description = body === '' ? (metaContent(html, 'og:description') ?? '') : body;
|
|
215
|
+
if (description === '') {
|
|
216
|
+
throw new JobImportProblem('That page has no readable text, so there is nothing to import.');
|
|
217
|
+
}
|
|
218
|
+
warnings.push('That page publishes no JobPosting data, so this was read off the page itself. Check every field before publishing.');
|
|
219
|
+
return { title, description, sourceUrl, via: 'page', warnings };
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=import-job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-job.js","sourceRoot":"","sources":["../../src/core/import-job.ts"],"names":[],"mappings":"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"}
|
package/dist/core/jobs.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ interface JobRow {
|
|
|
29
29
|
agent_policy: string;
|
|
30
30
|
apply_via: string;
|
|
31
31
|
apply_url: string | null;
|
|
32
|
+
apply_source_url: string | null;
|
|
32
33
|
apply_email: string | null;
|
|
33
34
|
apply_schema: ApplySchema | null;
|
|
34
35
|
status: string;
|
|
@@ -73,6 +74,8 @@ export interface JobInput {
|
|
|
73
74
|
agentPolicy: AgentPolicy;
|
|
74
75
|
apply: ApplyMethod;
|
|
75
76
|
expiresAt: string | null;
|
|
77
|
+
/** Where this listing was imported from, if it was. Provenance, not a destination. */
|
|
78
|
+
sourceUrl: string | null;
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
81
|
* Turn whatever a form, an API client or a model sent into a job we are
|
|
@@ -84,6 +87,32 @@ export declare function normaliseInput(input: Record<string, unknown>, orgId: st
|
|
|
84
87
|
export declare function normaliseApplySchema(input: unknown): ApplySchema | null;
|
|
85
88
|
export declare function createJob(pool: pg.Pool, input: JobInput): Promise<Job>;
|
|
86
89
|
export declare function setStatus(pool: pg.Pool, id: string, status: JobStatus): Promise<Job | null>;
|
|
90
|
+
/**
|
|
91
|
+
* Refresh a listing from a re-import.
|
|
92
|
+
*
|
|
93
|
+
* Only the fields an import can actually supply are touched, and only when the
|
|
94
|
+
* new value is present: a re-import that could not read a location must not
|
|
95
|
+
* erase one a person typed in. Status is untouched, so refreshing a published
|
|
96
|
+
* listing does not unpublish it and refreshing a draft does not publish it.
|
|
97
|
+
*/
|
|
98
|
+
export declare function updateJobFromImport(pool: pg.Pool, id: string, fields: {
|
|
99
|
+
title?: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
employmentType?: EmploymentType;
|
|
102
|
+
workplace?: Workplace;
|
|
103
|
+
location?: string;
|
|
104
|
+
sourceUrl?: string;
|
|
105
|
+
}): Promise<Job | null>;
|
|
106
|
+
/**
|
|
107
|
+
* Rewrite a listing's content.
|
|
108
|
+
*
|
|
109
|
+
* Takes the same shape `createJob` takes, so an edit cannot put a listing into
|
|
110
|
+
* a state a post could not have created. Slug, status and org are untouched:
|
|
111
|
+
* the slug is the listing's public URL and editing must not break links to it.
|
|
112
|
+
*/
|
|
113
|
+
export declare function editJob(pool: pg.Pool, id: string, input: JobInput): Promise<Job | null>;
|
|
114
|
+
/** The listing imported from this URL, if there is one. */
|
|
115
|
+
export declare function getJobBySourceUrl(pool: pg.Pool, url: string): Promise<Job | null>;
|
|
87
116
|
export declare function countJobs(pool: pg.Pool): Promise<{
|
|
88
117
|
open: number;
|
|
89
118
|
total: number;
|