@profullstack/agenticjobs 0.1.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/LICENSE +21 -0
- package/README.md +219 -0
- package/bin/agenticjobs-mcp.mjs +2 -0
- package/bin/agenticjobs.mjs +2 -0
- package/dist/cli/args.d.ts +17 -0
- package/dist/cli/args.js +103 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/format.d.ts +15 -0
- package/dist/cli/format.js +24 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.js +620 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/jobfile.d.ts +15 -0
- package/dist/cli/jobfile.js +94 -0
- package/dist/cli/jobfile.js.map +1 -0
- package/dist/client/client.d.ts +105 -0
- package/dist/client/client.js +182 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/config.d.ts +33 -0
- package/dist/client/config.js +92 -0
- package/dist/client/config.js.map +1 -0
- package/dist/client/fanout.d.ts +35 -0
- package/dist/client/fanout.js +77 -0
- package/dist/client/fanout.js.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +5 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/login.d.ts +23 -0
- package/dist/client/login.js +45 -0
- package/dist/client/login.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/core/applications.d.ts +67 -0
- package/dist/core/applications.js +187 -0
- package/dist/core/applications.js.map +1 -0
- package/dist/core/auth.d.ts +120 -0
- package/dist/core/auth.js +246 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/import.d.ts +52 -0
- package/dist/core/import.js +311 -0
- package/dist/core/import.js.map +1 -0
- package/dist/core/jobs.d.ts +91 -0
- package/dist/core/jobs.js +381 -0
- package/dist/core/jobs.js.map +1 -0
- package/dist/core/orgs.d.ts +21 -0
- package/dist/core/orgs.js +111 -0
- package/dist/core/orgs.js.map +1 -0
- package/dist/core/resumes.d.ts +51 -0
- package/dist/core/resumes.js +144 -0
- package/dist/core/resumes.js.map +1 -0
- package/dist/core/zip.d.ts +17 -0
- package/dist/core/zip.js +77 -0
- package/dist/core/zip.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +15 -0
- package/dist/db/migrate.js +67 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/pool.d.ts +13 -0
- package/dist/db/pool.js +68 -0
- package/dist/db/pool.js.map +1 -0
- package/dist/db/seed.d.ts +9 -0
- package/dist/db/seed.js +120 -0
- package/dist/db/seed.js.map +1 -0
- package/dist/directory/announce.d.ts +32 -0
- package/dist/directory/announce.js +59 -0
- package/dist/directory/announce.js.map +1 -0
- package/dist/directory/federate.d.ts +59 -0
- package/dist/directory/federate.js +126 -0
- package/dist/directory/federate.js.map +1 -0
- package/dist/directory/fetch.d.ts +35 -0
- package/dist/directory/fetch.js +138 -0
- package/dist/directory/fetch.js.map +1 -0
- package/dist/directory/index.d.ts +4 -0
- package/dist/directory/index.js +5 -0
- package/dist/directory/index.js.map +1 -0
- package/dist/directory/registry.d.ts +55 -0
- package/dist/directory/registry.js +155 -0
- package/dist/directory/registry.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/markup/escape.d.ts +20 -0
- package/dist/markup/escape.js +56 -0
- package/dist/markup/escape.js.map +1 -0
- package/dist/markup/markdown.d.ts +35 -0
- package/dist/markup/markdown.js +279 -0
- package/dist/markup/markdown.js.map +1 -0
- package/dist/markup/resume.d.ts +72 -0
- package/dist/markup/resume.js +260 -0
- package/dist/markup/resume.js.map +1 -0
- package/dist/mcp/protocol.d.ts +56 -0
- package/dist/mcp/protocol.js +48 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +21 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +13 -0
- package/dist/mcp/stdio.js +85 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.js +351 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +6 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/instance.d.ts +85 -0
- package/dist/schema/instance.js +147 -0
- package/dist/schema/instance.js.map +1 -0
- package/dist/schema/job.d.ts +151 -0
- package/dist/schema/job.js +73 -0
- package/dist/schema/job.js.map +1 -0
- package/dist/schema/jsonld.d.ts +18 -0
- package/dist/schema/jsonld.js +120 -0
- package/dist/schema/jsonld.js.map +1 -0
- package/dist/schema/query.d.ts +47 -0
- package/dist/schema/query.js +103 -0
- package/dist/schema/query.js.map +1 -0
- package/dist/schema/text.d.ts +27 -0
- package/dist/schema/text.js +117 -0
- package/dist/schema/text.js.map +1 -0
- package/dist/server/app.d.ts +13 -0
- package/dist/server/app.js +57 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/deps.d.ts +20 -0
- package/dist/server/deps.js +2 -0
- package/dist/server/deps.js.map +1 -0
- package/dist/server/middleware.d.ts +39 -0
- package/dist/server/middleware.js +100 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/routes/api.d.ts +20 -0
- package/dist/server/routes/api.js +584 -0
- package/dist/server/routes/api.js.map +1 -0
- package/dist/server/routes/descriptor.d.ts +11 -0
- package/dist/server/routes/descriptor.js +31 -0
- package/dist/server/routes/descriptor.js.map +1 -0
- package/dist/server/routes/discovery.d.ts +12 -0
- package/dist/server/routes/discovery.js +305 -0
- package/dist/server/routes/discovery.js.map +1 -0
- package/dist/server/routes/mcp.d.ts +19 -0
- package/dist/server/routes/mcp.js +85 -0
- package/dist/server/routes/mcp.js.map +1 -0
- package/dist/server/routes/openapi.d.ts +11 -0
- package/dist/server/routes/openapi.js +297 -0
- package/dist/server/routes/openapi.js.map +1 -0
- package/dist/server/routes/pages.d.ts +11 -0
- package/dist/server/routes/pages.js +445 -0
- package/dist/server/routes/pages.js.map +1 -0
- package/dist/server/routes/passkey.d.ts +20 -0
- package/dist/server/routes/passkey.js +198 -0
- package/dist/server/routes/passkey.js.map +1 -0
- package/dist/server/routes/specs.d.ts +10 -0
- package/dist/server/routes/specs.js +42 -0
- package/dist/server/routes/specs.js.map +1 -0
- package/dist/server/serve.d.ts +11 -0
- package/dist/server/serve.js +79 -0
- package/dist/server/serve.js.map +1 -0
- package/dist/tui/index.d.ts +13 -0
- package/dist/tui/index.js +266 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/state.d.ts +63 -0
- package/dist/tui/state.js +136 -0
- package/dist/tui/state.js.map +1 -0
- package/dist/tui/types.d.ts +2 -0
- package/dist/tui/types.js +2 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/tui/views.d.ts +11 -0
- package/dist/tui/views.js +197 -0
- package/dist/tui/views.js.map +1 -0
- package/dist/views/auth.d.ts +21 -0
- package/dist/views/auth.js +5 -0
- package/dist/views/auth.js.map +1 -0
- package/dist/views/docs.d.ts +17 -0
- package/dist/views/docs.js +40 -0
- package/dist/views/docs.js.map +1 -0
- package/dist/views/jobs.d.ts +49 -0
- package/dist/views/jobs.js +44 -0
- package/dist/views/jobs.js.map +1 -0
- package/dist/views/layout.d.ts +58 -0
- package/dist/views/layout.js +36 -0
- package/dist/views/layout.js.map +1 -0
- package/dist/views/me.d.ts +27 -0
- package/dist/views/me.js +6 -0
- package/dist/views/me.js.map +1 -0
- package/dist/views/network.d.ts +21 -0
- package/dist/views/network.js +17 -0
- package/dist/views/network.js.map +1 -0
- package/dist/views/post.d.ts +22 -0
- package/dist/views/post.js +20 -0
- package/dist/views/post.js.map +1 -0
- package/docs/openjob.md +200 -0
- package/docs/openresume.md +143 -0
- package/migrations/0001_init.sql +109 -0
- package/migrations/0002_auth.sql +82 -0
- package/migrations/0003_directory.sql +27 -0
- package/migrations/0004_resumes.sql +63 -0
- package/migrations/0005_application_drafts.sql +20 -0
- package/package.json +92 -0
- package/web/public/app.css +731 -0
- package/web/public/app.js +173 -0
- package/web/public/icon.svg +5 -0
- package/web/public/sw.js +64 -0
- package/web/public/tokens.css +153 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and writing jobs.
|
|
3
|
+
*
|
|
4
|
+
* Every surface goes through here, and all of them get the same rows: the
|
|
5
|
+
* pages, the REST API, MCP, the CLI, the TUI and a federated search arriving
|
|
6
|
+
* from another instance. There is deliberately no second read path.
|
|
7
|
+
*/
|
|
8
|
+
import type pg from 'pg';
|
|
9
|
+
import { type AgentPolicy, type ApplyMethod, type ApplySchema, type EmploymentType, type Job, type JobPage, type JobQuery, type JobStatus, type Seniority, type Workplace } from '../schema/index.ts';
|
|
10
|
+
interface JobRow {
|
|
11
|
+
id: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
employment_type: string;
|
|
16
|
+
workplace: string;
|
|
17
|
+
seniority: string | null;
|
|
18
|
+
location: string | null;
|
|
19
|
+
remote_regions: string[];
|
|
20
|
+
salary_min: number | null;
|
|
21
|
+
salary_max: number | null;
|
|
22
|
+
salary_currency: string;
|
|
23
|
+
salary_period: string;
|
|
24
|
+
salary_equity: string | null;
|
|
25
|
+
tags: string[];
|
|
26
|
+
stack: string[];
|
|
27
|
+
requirements: string[];
|
|
28
|
+
responsibilities: string[];
|
|
29
|
+
agent_policy: string;
|
|
30
|
+
apply_via: string;
|
|
31
|
+
apply_url: string | null;
|
|
32
|
+
apply_email: string | null;
|
|
33
|
+
apply_schema: ApplySchema | null;
|
|
34
|
+
status: string;
|
|
35
|
+
published_at: string | null;
|
|
36
|
+
expires_at: string | null;
|
|
37
|
+
created_at: string;
|
|
38
|
+
updated_at: string;
|
|
39
|
+
org_id: string;
|
|
40
|
+
org_slug: string;
|
|
41
|
+
org_name: string;
|
|
42
|
+
org_website: string | null;
|
|
43
|
+
org_logo_url: string | null;
|
|
44
|
+
org_description: string | null;
|
|
45
|
+
org_created_at: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function toJob(row: JobRow): Job;
|
|
48
|
+
export declare function searchJobs(pool: pg.Pool, query: JobQuery, options?: {
|
|
49
|
+
includeUnpublished?: boolean;
|
|
50
|
+
}): Promise<JobPage<Job>>;
|
|
51
|
+
export declare function getJobBySlug(pool: pg.Pool, slug: string, options?: {
|
|
52
|
+
includeUnpublished?: boolean;
|
|
53
|
+
}): Promise<Job | null>;
|
|
54
|
+
export declare function getJobById(pool: pg.Pool, id: string): Promise<Job | null>;
|
|
55
|
+
export interface JobInput {
|
|
56
|
+
orgId: string;
|
|
57
|
+
title: string;
|
|
58
|
+
description: string;
|
|
59
|
+
employmentType: EmploymentType;
|
|
60
|
+
workplace: Workplace;
|
|
61
|
+
seniority: Seniority | null;
|
|
62
|
+
location: string | null;
|
|
63
|
+
remoteRegions: string[];
|
|
64
|
+
salaryMin: number | null;
|
|
65
|
+
salaryMax: number | null;
|
|
66
|
+
salaryCurrency: string;
|
|
67
|
+
salaryPeriod: string;
|
|
68
|
+
salaryEquity: string | null;
|
|
69
|
+
tags: string[];
|
|
70
|
+
stack: string[];
|
|
71
|
+
requirements: string[];
|
|
72
|
+
responsibilities: string[];
|
|
73
|
+
agentPolicy: AgentPolicy;
|
|
74
|
+
apply: ApplyMethod;
|
|
75
|
+
expiresAt: string | null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Turn whatever a form, an API client or a model sent into a job we are
|
|
79
|
+
* willing to store. Anything unrecognised falls back rather than throwing:
|
|
80
|
+
* a posting rejected wholesale because one enum was misspelled is a posting
|
|
81
|
+
* an agent cannot fix without a human.
|
|
82
|
+
*/
|
|
83
|
+
export declare function normaliseInput(input: Record<string, unknown>, orgId: string): JobInput | string;
|
|
84
|
+
export declare function normaliseApplySchema(input: unknown): ApplySchema | null;
|
|
85
|
+
export declare function createJob(pool: pg.Pool, input: JobInput): Promise<Job>;
|
|
86
|
+
export declare function setStatus(pool: pg.Pool, id: string, status: JobStatus): Promise<Job | null>;
|
|
87
|
+
export declare function countJobs(pool: pg.Pool): Promise<{
|
|
88
|
+
open: number;
|
|
89
|
+
total: number;
|
|
90
|
+
}>;
|
|
91
|
+
export {};
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and writing jobs.
|
|
3
|
+
*
|
|
4
|
+
* Every surface goes through here, and all of them get the same rows: the
|
|
5
|
+
* pages, the REST API, MCP, the CLI, the TUI and a federated search arriving
|
|
6
|
+
* from another instance. There is deliberately no second read path.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_APPLY_SCHEMA, isAgentPolicy, isEmploymentType, isSeniority, isSalaryPeriod, isWorkplace, } from "../schema/index.js";
|
|
9
|
+
import { clean, parseList, slugify, suffix } from "../schema/text.js";
|
|
10
|
+
const SELECT = `
|
|
11
|
+
select j.id, j.slug, j.title, j.description, j.employment_type, j.workplace, j.seniority,
|
|
12
|
+
j.location, j.remote_regions, j.salary_min, j.salary_max, j.salary_currency,
|
|
13
|
+
j.salary_period, j.salary_equity, j.tags, j.stack, j.requirements, j.responsibilities,
|
|
14
|
+
j.agent_policy, j.apply_via, j.apply_url, j.apply_email, j.apply_schema, j.status,
|
|
15
|
+
j.published_at, j.expires_at, j.created_at, j.updated_at,
|
|
16
|
+
o.id as org_id, o.slug as org_slug, o.name as org_name, o.website as org_website,
|
|
17
|
+
o.logo_url as org_logo_url, o.description as org_description,
|
|
18
|
+
o.created_at as org_created_at
|
|
19
|
+
from jobs j
|
|
20
|
+
join organisations o on o.id = j.org_id
|
|
21
|
+
`;
|
|
22
|
+
function toOrganisation(row) {
|
|
23
|
+
return {
|
|
24
|
+
id: row.org_id,
|
|
25
|
+
slug: row.org_slug,
|
|
26
|
+
name: row.org_name,
|
|
27
|
+
website: row.org_website,
|
|
28
|
+
logoUrl: row.org_logo_url,
|
|
29
|
+
description: row.org_description,
|
|
30
|
+
createdAt: row.org_created_at,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function toApplyMethod(row) {
|
|
34
|
+
if (row.apply_via === 'url' && row.apply_url)
|
|
35
|
+
return { via: 'url', url: row.apply_url };
|
|
36
|
+
if (row.apply_via === 'email' && row.apply_email)
|
|
37
|
+
return { via: 'email', email: row.apply_email };
|
|
38
|
+
// A job whose declared method lost its target still has to be applicable,
|
|
39
|
+
// and the board's own form is the one target that cannot go missing.
|
|
40
|
+
return { via: 'board', schema: row.apply_schema ?? DEFAULT_APPLY_SCHEMA };
|
|
41
|
+
}
|
|
42
|
+
export function toJob(row) {
|
|
43
|
+
return {
|
|
44
|
+
id: row.id,
|
|
45
|
+
slug: row.slug,
|
|
46
|
+
org: toOrganisation(row),
|
|
47
|
+
title: row.title,
|
|
48
|
+
description: row.description,
|
|
49
|
+
employmentType: isEmploymentType(row.employment_type)
|
|
50
|
+
? row.employment_type
|
|
51
|
+
: 'full-time',
|
|
52
|
+
workplace: isWorkplace(row.workplace) ? row.workplace : 'remote',
|
|
53
|
+
seniority: isSeniority(row.seniority) ? row.seniority : null,
|
|
54
|
+
location: row.location,
|
|
55
|
+
remoteRegions: row.remote_regions ?? [],
|
|
56
|
+
salary: {
|
|
57
|
+
min: row.salary_min,
|
|
58
|
+
max: row.salary_max,
|
|
59
|
+
currency: row.salary_currency,
|
|
60
|
+
period: isSalaryPeriod(row.salary_period) ? row.salary_period : 'year',
|
|
61
|
+
equity: row.salary_equity,
|
|
62
|
+
},
|
|
63
|
+
tags: row.tags ?? [],
|
|
64
|
+
stack: row.stack ?? [],
|
|
65
|
+
requirements: row.requirements ?? [],
|
|
66
|
+
responsibilities: row.responsibilities ?? [],
|
|
67
|
+
agentPolicy: isAgentPolicy(row.agent_policy) ? row.agent_policy : 'disclose',
|
|
68
|
+
apply: toApplyMethod(row),
|
|
69
|
+
status: ['draft', 'published', 'closed'].includes(row.status)
|
|
70
|
+
? row.status
|
|
71
|
+
: 'draft',
|
|
72
|
+
publishedAt: row.published_at,
|
|
73
|
+
expiresAt: row.expires_at,
|
|
74
|
+
createdAt: row.created_at,
|
|
75
|
+
updatedAt: row.updated_at,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Builds the where clause shared by search and count, so they cannot drift. */
|
|
79
|
+
function conditions(query, includeUnpublished) {
|
|
80
|
+
const params = [];
|
|
81
|
+
const where = [];
|
|
82
|
+
if (!includeUnpublished) {
|
|
83
|
+
where.push(`j.status = 'published'`);
|
|
84
|
+
where.push(`j.published_at is not null and j.published_at <= now()`);
|
|
85
|
+
// An expired listing stops appearing without anyone having to run a sweep.
|
|
86
|
+
where.push(`(j.expires_at is null or j.expires_at > now())`);
|
|
87
|
+
}
|
|
88
|
+
if (query.q !== null) {
|
|
89
|
+
params.push(query.q);
|
|
90
|
+
where.push(`j.search @@ websearch_to_tsquery('english', $${params.length})`);
|
|
91
|
+
}
|
|
92
|
+
if (query.employmentType !== null) {
|
|
93
|
+
params.push(query.employmentType);
|
|
94
|
+
where.push(`j.employment_type = $${params.length}`);
|
|
95
|
+
}
|
|
96
|
+
if (query.workplace !== null) {
|
|
97
|
+
params.push(query.workplace);
|
|
98
|
+
where.push(`j.workplace = $${params.length}`);
|
|
99
|
+
}
|
|
100
|
+
if (query.seniority !== null) {
|
|
101
|
+
params.push(query.seniority);
|
|
102
|
+
where.push(`j.seniority = $${params.length}`);
|
|
103
|
+
}
|
|
104
|
+
if (query.agentPolicy !== null) {
|
|
105
|
+
params.push(query.agentPolicy);
|
|
106
|
+
where.push(`j.agent_policy = $${params.length}`);
|
|
107
|
+
}
|
|
108
|
+
if (query.tags.length > 0) {
|
|
109
|
+
params.push(query.tags);
|
|
110
|
+
// Matching either list means "python" finds a job that called it a tag and
|
|
111
|
+
// one that called it part of the stack.
|
|
112
|
+
where.push(`(j.tags && $${params.length}::text[] or j.stack && $${params.length}::text[])`);
|
|
113
|
+
}
|
|
114
|
+
if (query.salaryMin !== null) {
|
|
115
|
+
params.push(query.salaryMin);
|
|
116
|
+
// Compared against the top of the range: a listing that says 90k-140k is a
|
|
117
|
+
// match for someone who needs 120k, and one that says 90k-100k is not.
|
|
118
|
+
where.push(`coalesce(j.salary_max, j.salary_min) >= $${params.length}`);
|
|
119
|
+
}
|
|
120
|
+
if (query.org !== null) {
|
|
121
|
+
params.push(query.org);
|
|
122
|
+
where.push(`o.slug = $${params.length}`);
|
|
123
|
+
}
|
|
124
|
+
return { sql: where.length === 0 ? '' : `where ${where.join(' and ')}`, params };
|
|
125
|
+
}
|
|
126
|
+
function orderBy(query) {
|
|
127
|
+
if (query.sort === 'salary') {
|
|
128
|
+
return `order by coalesce(j.salary_max, j.salary_min, 0) desc, j.published_at desc nulls last`;
|
|
129
|
+
}
|
|
130
|
+
if (query.sort === 'relevant' && query.q !== null) {
|
|
131
|
+
// ts_rank against the same tsquery the where clause used. Without a query
|
|
132
|
+
// string there is nothing to rank against, so this falls through.
|
|
133
|
+
return `order by ts_rank(j.search, websearch_to_tsquery('english', $1)) desc, j.published_at desc nulls last`;
|
|
134
|
+
}
|
|
135
|
+
return `order by j.published_at desc nulls last, j.created_at desc`;
|
|
136
|
+
}
|
|
137
|
+
export async function searchJobs(pool, query, options = {}) {
|
|
138
|
+
const includeUnpublished = options.includeUnpublished === true;
|
|
139
|
+
const { sql: whereSql, params } = conditions(query, includeUnpublished);
|
|
140
|
+
const counted = await pool.query(`select count(*)::bigint as total from jobs j join organisations o on o.id = j.org_id ${whereSql}`, params);
|
|
141
|
+
const listParams = [...params, query.limit, query.offset];
|
|
142
|
+
const rows = await pool.query(`${SELECT} ${whereSql} ${orderBy(query)} limit $${listParams.length - 1} offset $${listParams.length}`, listParams);
|
|
143
|
+
return {
|
|
144
|
+
items: rows.rows.map(toJob),
|
|
145
|
+
total: counted.rows[0]?.total ?? 0,
|
|
146
|
+
limit: query.limit,
|
|
147
|
+
offset: query.offset,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export async function getJobBySlug(pool, slug, options = {}) {
|
|
151
|
+
const visible = options.includeUnpublished === true
|
|
152
|
+
? ''
|
|
153
|
+
: `and j.status = 'published' and j.published_at is not null and j.published_at <= now()`;
|
|
154
|
+
const result = await pool.query(`${SELECT} where j.slug = $1 ${visible} limit 1`, [slug]);
|
|
155
|
+
const row = result.rows[0];
|
|
156
|
+
return row === undefined ? null : toJob(row);
|
|
157
|
+
}
|
|
158
|
+
export async function getJobById(pool, id) {
|
|
159
|
+
const result = await pool.query(`${SELECT} where j.id = $1 limit 1`, [id]);
|
|
160
|
+
const row = result.rows[0];
|
|
161
|
+
return row === undefined ? null : toJob(row);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Turn whatever a form, an API client or a model sent into a job we are
|
|
165
|
+
* willing to store. Anything unrecognised falls back rather than throwing:
|
|
166
|
+
* a posting rejected wholesale because one enum was misspelled is a posting
|
|
167
|
+
* an agent cannot fix without a human.
|
|
168
|
+
*/
|
|
169
|
+
export function normaliseInput(input, orgId) {
|
|
170
|
+
const title = clean(input['title'], 140);
|
|
171
|
+
if (title.length < 3)
|
|
172
|
+
return 'A title of at least 3 characters is required.';
|
|
173
|
+
const description = clean(input['description'], 20_000);
|
|
174
|
+
if (description.length < 20)
|
|
175
|
+
return 'A description of at least 20 characters is required.';
|
|
176
|
+
const employmentType = isEmploymentType(input['employmentType'])
|
|
177
|
+
? input['employmentType']
|
|
178
|
+
: 'full-time';
|
|
179
|
+
const workplace = isWorkplace(input['workplace']) ? input['workplace'] : 'remote';
|
|
180
|
+
const seniority = isSeniority(input['seniority']) ? input['seniority'] : null;
|
|
181
|
+
const agentPolicy = isAgentPolicy(input['agentPolicy']) ? input['agentPolicy'] : 'disclose';
|
|
182
|
+
const salaryMin = money(input['salaryMin']);
|
|
183
|
+
const salaryMax = money(input['salaryMax']);
|
|
184
|
+
if (salaryMin !== null && salaryMax !== null && salaryMax < salaryMin) {
|
|
185
|
+
return 'The top of the salary range is below the bottom of it.';
|
|
186
|
+
}
|
|
187
|
+
const apply = normaliseApply(input);
|
|
188
|
+
if (typeof apply === 'string')
|
|
189
|
+
return apply;
|
|
190
|
+
const location = clean(input['location'], 120);
|
|
191
|
+
return {
|
|
192
|
+
orgId,
|
|
193
|
+
title,
|
|
194
|
+
description,
|
|
195
|
+
employmentType,
|
|
196
|
+
workplace,
|
|
197
|
+
seniority,
|
|
198
|
+
location: location === '' ? null : location,
|
|
199
|
+
remoteRegions: parseList(input['remoteRegions'], 30, 8).map((code) => code.toUpperCase()),
|
|
200
|
+
salaryMin,
|
|
201
|
+
salaryMax,
|
|
202
|
+
salaryCurrency: (clean(input['salaryCurrency'], 3) || 'USD').toUpperCase(),
|
|
203
|
+
salaryPeriod: isSalaryPeriod(input['salaryPeriod']) ? input['salaryPeriod'] : 'year',
|
|
204
|
+
salaryEquity: clean(input['salaryEquity'], 60) || null,
|
|
205
|
+
tags: parseList(input['tags'], 12),
|
|
206
|
+
stack: parseList(input['stack'], 20),
|
|
207
|
+
requirements: lines(input['requirements'], 20),
|
|
208
|
+
responsibilities: lines(input['responsibilities'], 20),
|
|
209
|
+
agentPolicy,
|
|
210
|
+
apply,
|
|
211
|
+
expiresAt: date(input['expiresAt']),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function normaliseApply(input) {
|
|
215
|
+
const via = clean(input['applyVia'], 10) || 'board';
|
|
216
|
+
if (via === 'url') {
|
|
217
|
+
const url = clean(input['applyUrl'], 500);
|
|
218
|
+
try {
|
|
219
|
+
const parsed = new URL(url);
|
|
220
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
221
|
+
return 'An application link must be http or https.';
|
|
222
|
+
}
|
|
223
|
+
return { via: 'url', url: parsed.toString() };
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return 'That application link is not a URL.';
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (via === 'email') {
|
|
230
|
+
const email = clean(input['applyEmail'], 200).toLowerCase();
|
|
231
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
|
|
232
|
+
return 'That application address is not valid.';
|
|
233
|
+
return { via: 'email', email };
|
|
234
|
+
}
|
|
235
|
+
const schema = input['applySchema'];
|
|
236
|
+
if (schema !== undefined && schema !== null) {
|
|
237
|
+
const parsed = normaliseApplySchema(schema);
|
|
238
|
+
if (parsed !== null)
|
|
239
|
+
return { via: 'board', schema: parsed };
|
|
240
|
+
}
|
|
241
|
+
return { via: 'board', schema: DEFAULT_APPLY_SCHEMA };
|
|
242
|
+
}
|
|
243
|
+
const FIELD_TYPES = ['text', 'textarea', 'email', 'url', 'select', 'file'];
|
|
244
|
+
export function normaliseApplySchema(input) {
|
|
245
|
+
if (typeof input !== 'object' || input === null)
|
|
246
|
+
return null;
|
|
247
|
+
const fields = input['fields'];
|
|
248
|
+
if (!Array.isArray(fields))
|
|
249
|
+
return null;
|
|
250
|
+
const seen = new Set();
|
|
251
|
+
const out = [];
|
|
252
|
+
for (const raw of fields.slice(0, 25)) {
|
|
253
|
+
if (typeof raw !== 'object' || raw === null)
|
|
254
|
+
continue;
|
|
255
|
+
const field = raw;
|
|
256
|
+
// The name becomes a form input name and a JSON key on the way back in,
|
|
257
|
+
// so it is restricted rather than cleaned.
|
|
258
|
+
const name = clean(field['name'], 40).replace(/[^a-zA-Z0-9_]/g, '');
|
|
259
|
+
if (name === '' || seen.has(name))
|
|
260
|
+
continue;
|
|
261
|
+
seen.add(name);
|
|
262
|
+
const type = clean(field['type'], 10);
|
|
263
|
+
out.push({
|
|
264
|
+
name,
|
|
265
|
+
label: clean(field['label'], 120) || name,
|
|
266
|
+
type: FIELD_TYPES.includes(type)
|
|
267
|
+
? type
|
|
268
|
+
: 'text',
|
|
269
|
+
required: field['required'] === true,
|
|
270
|
+
...(Array.isArray(field['options'])
|
|
271
|
+
? { options: parseList(field['options'], 30, 80) }
|
|
272
|
+
: {}),
|
|
273
|
+
...(clean(field['help'], 200) ? { help: clean(field['help'], 200) } : {}),
|
|
274
|
+
maxLength: Math.min(20_000, Math.max(1, Number(field['maxLength']) || 2000)),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return out.length === 0 ? null : { fields: out };
|
|
278
|
+
}
|
|
279
|
+
function money(value) {
|
|
280
|
+
if (value === null || value === undefined || value === '')
|
|
281
|
+
return null;
|
|
282
|
+
const parsed = Number.parseInt(String(value).replace(/[^0-9]/g, ''), 10);
|
|
283
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
284
|
+
return null;
|
|
285
|
+
return Math.min(100_000_000, parsed);
|
|
286
|
+
}
|
|
287
|
+
function date(value) {
|
|
288
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
289
|
+
return null;
|
|
290
|
+
const parsed = Date.parse(value);
|
|
291
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
292
|
+
}
|
|
293
|
+
function lines(value, max) {
|
|
294
|
+
const raw = Array.isArray(value)
|
|
295
|
+
? value
|
|
296
|
+
: typeof value === 'string'
|
|
297
|
+
? value.split(/\r?\n/)
|
|
298
|
+
: [];
|
|
299
|
+
return raw
|
|
300
|
+
.map((line) => clean(line, 300).replace(/^[-*]\s*/, ''))
|
|
301
|
+
.filter((line) => line !== '')
|
|
302
|
+
.slice(0, max);
|
|
303
|
+
}
|
|
304
|
+
/** Reserve a slug, adding a suffix only when the plain one is taken. */
|
|
305
|
+
async function uniqueSlug(pool, title) {
|
|
306
|
+
const base = slugify(title);
|
|
307
|
+
const taken = await pool.query(`select slug from jobs where slug = $1 or slug like $2`, [base, `${base}-%`]);
|
|
308
|
+
if (!taken.rows.some((row) => row.slug === base))
|
|
309
|
+
return base;
|
|
310
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
311
|
+
const candidate = `${base}-${suffix()}`;
|
|
312
|
+
if (!taken.rows.some((row) => row.slug === candidate))
|
|
313
|
+
return candidate;
|
|
314
|
+
}
|
|
315
|
+
return `${base}-${Date.now().toString(36)}`;
|
|
316
|
+
}
|
|
317
|
+
export async function createJob(pool, input) {
|
|
318
|
+
const slug = await uniqueSlug(pool, input.title);
|
|
319
|
+
const result = await pool.query(`insert into jobs (
|
|
320
|
+
slug, org_id, title, description, employment_type, workplace, seniority, location,
|
|
321
|
+
remote_regions, salary_min, salary_max, salary_currency, salary_period, salary_equity,
|
|
322
|
+
tags, stack, requirements, responsibilities, agent_policy, apply_via, apply_url,
|
|
323
|
+
apply_email, apply_schema, expires_at, status
|
|
324
|
+
) values (
|
|
325
|
+
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,'draft'
|
|
326
|
+
) returning id`, [
|
|
327
|
+
slug,
|
|
328
|
+
input.orgId,
|
|
329
|
+
input.title,
|
|
330
|
+
input.description,
|
|
331
|
+
input.employmentType,
|
|
332
|
+
input.workplace,
|
|
333
|
+
input.seniority,
|
|
334
|
+
input.location,
|
|
335
|
+
input.remoteRegions,
|
|
336
|
+
input.salaryMin,
|
|
337
|
+
input.salaryMax,
|
|
338
|
+
input.salaryCurrency,
|
|
339
|
+
input.salaryPeriod,
|
|
340
|
+
input.salaryEquity,
|
|
341
|
+
input.tags,
|
|
342
|
+
input.stack,
|
|
343
|
+
input.requirements,
|
|
344
|
+
input.responsibilities,
|
|
345
|
+
input.agentPolicy,
|
|
346
|
+
input.apply.via,
|
|
347
|
+
input.apply.via === 'url' ? input.apply.url : null,
|
|
348
|
+
input.apply.via === 'email' ? input.apply.email : null,
|
|
349
|
+
input.apply.via === 'board' ? JSON.stringify(input.apply.schema) : null,
|
|
350
|
+
input.expiresAt,
|
|
351
|
+
]);
|
|
352
|
+
const id = result.rows[0]?.id;
|
|
353
|
+
if (id === undefined)
|
|
354
|
+
throw new Error('insert returned no row');
|
|
355
|
+
const job = await getJobById(pool, id);
|
|
356
|
+
if (job === null)
|
|
357
|
+
throw new Error('inserted job could not be read back');
|
|
358
|
+
return job;
|
|
359
|
+
}
|
|
360
|
+
export async function setStatus(pool, id, status) {
|
|
361
|
+
await pool.query(`update jobs
|
|
362
|
+
set status = $2,
|
|
363
|
+
published_at = case
|
|
364
|
+
when $2 = 'published' and published_at is null then now()
|
|
365
|
+
else published_at
|
|
366
|
+
end
|
|
367
|
+
where id = $1`, [id, status]);
|
|
368
|
+
return getJobById(pool, id);
|
|
369
|
+
}
|
|
370
|
+
export async function countJobs(pool) {
|
|
371
|
+
const result = await pool.query(`select
|
|
372
|
+
count(*) filter (
|
|
373
|
+
where status = 'published'
|
|
374
|
+
and published_at is not null and published_at <= now()
|
|
375
|
+
and (expires_at is null or expires_at > now())
|
|
376
|
+
)::bigint as open,
|
|
377
|
+
count(*)::bigint as total
|
|
378
|
+
from jobs`);
|
|
379
|
+
return { open: result.rows[0]?.open ?? 0, total: result.rows[0]?.total ?? 0 };
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/core/jobs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Employers.
|
|
3
|
+
*
|
|
4
|
+
* A person can belong to several, which is the difference between a job board
|
|
5
|
+
* and a blog with a jobs page: the agency posting on behalf of four clients is
|
|
6
|
+
* the normal case, not the exception.
|
|
7
|
+
*/
|
|
8
|
+
import type pg from 'pg';
|
|
9
|
+
import type { Organisation } from '../schema/index.ts';
|
|
10
|
+
export declare function getOrgBySlug(pool: pg.Pool, slug: string): Promise<Organisation | null>;
|
|
11
|
+
export declare function getOrgById(pool: pg.Pool, id: string): Promise<Organisation | null>;
|
|
12
|
+
export declare function listOrgsForUser(pool: pg.Pool, userId: string): Promise<Organisation[]>;
|
|
13
|
+
export declare function isMember(pool: pg.Pool, userId: string, orgId: string): Promise<boolean>;
|
|
14
|
+
export interface OrgInput {
|
|
15
|
+
name: string;
|
|
16
|
+
website?: string | null;
|
|
17
|
+
description?: string | null;
|
|
18
|
+
logoUrl?: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function createOrg(pool: pg.Pool, ownerId: string, input: OrgInput): Promise<Organisation | string>;
|
|
21
|
+
export declare function listOrgs(pool: pg.Pool, limit?: number): Promise<Organisation[]>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Employers.
|
|
3
|
+
*
|
|
4
|
+
* A person can belong to several, which is the difference between a job board
|
|
5
|
+
* and a blog with a jobs page: the agency posting on behalf of four clients is
|
|
6
|
+
* the normal case, not the exception.
|
|
7
|
+
*/
|
|
8
|
+
import { clean, slugify, suffix } from "../schema/text.js";
|
|
9
|
+
function toOrg(row) {
|
|
10
|
+
return {
|
|
11
|
+
id: row.id,
|
|
12
|
+
slug: row.slug,
|
|
13
|
+
name: row.name,
|
|
14
|
+
website: row.website,
|
|
15
|
+
logoUrl: row.logo_url,
|
|
16
|
+
description: row.description,
|
|
17
|
+
createdAt: row.created_at,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const SELECT = `select id, slug, name, website, logo_url, description, created_at from organisations`;
|
|
21
|
+
export async function getOrgBySlug(pool, slug) {
|
|
22
|
+
const result = await pool.query(`${SELECT} where slug = $1`, [slug]);
|
|
23
|
+
const row = result.rows[0];
|
|
24
|
+
return row === undefined ? null : toOrg(row);
|
|
25
|
+
}
|
|
26
|
+
export async function getOrgById(pool, id) {
|
|
27
|
+
const result = await pool.query(`${SELECT} where id = $1`, [id]);
|
|
28
|
+
const row = result.rows[0];
|
|
29
|
+
return row === undefined ? null : toOrg(row);
|
|
30
|
+
}
|
|
31
|
+
export async function listOrgsForUser(pool, userId) {
|
|
32
|
+
const result = await pool.query(`select o.id, o.slug, o.name, o.website, o.logo_url, o.description, o.created_at
|
|
33
|
+
from organisations o
|
|
34
|
+
join memberships m on m.org_id = o.id
|
|
35
|
+
where m.user_id = $1
|
|
36
|
+
order by o.name asc`, [userId]);
|
|
37
|
+
return result.rows.map(toOrg);
|
|
38
|
+
}
|
|
39
|
+
export async function isMember(pool, userId, orgId) {
|
|
40
|
+
const result = await pool.query(`select 1 from memberships where user_id = $1 and org_id = $2`, [userId, orgId]);
|
|
41
|
+
return result.rows.length > 0;
|
|
42
|
+
}
|
|
43
|
+
function normaliseUrl(value) {
|
|
44
|
+
const raw = clean(value, 500);
|
|
45
|
+
if (raw === '')
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(raw.includes('://') ? raw : `https://${raw}`);
|
|
49
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
50
|
+
return null;
|
|
51
|
+
return url.toString();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function createOrg(pool, ownerId, input) {
|
|
58
|
+
const name = clean(input.name, 120);
|
|
59
|
+
if (name.length < 2)
|
|
60
|
+
return 'An employer name of at least 2 characters is required.';
|
|
61
|
+
const base = slugify(name);
|
|
62
|
+
let slug = base;
|
|
63
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
64
|
+
const taken = await pool.query(`select 1 from organisations where slug = $1`, [slug]);
|
|
65
|
+
if (taken.rows.length === 0)
|
|
66
|
+
break;
|
|
67
|
+
slug = `${base}-${suffix(4)}`;
|
|
68
|
+
}
|
|
69
|
+
const client = await pool.connect();
|
|
70
|
+
try {
|
|
71
|
+
await client.query('begin');
|
|
72
|
+
const created = await client.query(`insert into organisations (slug, name, website, description, logo_url)
|
|
73
|
+
values ($1, $2, $3, $4, $5)
|
|
74
|
+
returning id, slug, name, website, logo_url, description, created_at`, [
|
|
75
|
+
slug,
|
|
76
|
+
name,
|
|
77
|
+
normaliseUrl(input.website),
|
|
78
|
+
clean(input.description, 2000) || null,
|
|
79
|
+
normaliseUrl(input.logoUrl),
|
|
80
|
+
]);
|
|
81
|
+
const row = created.rows[0];
|
|
82
|
+
if (row === undefined)
|
|
83
|
+
throw new Error('organisation insert returned no row');
|
|
84
|
+
// The membership is part of the same transaction: an employer with no
|
|
85
|
+
// members is one nobody can post to or delete.
|
|
86
|
+
await client.query(`insert into memberships (user_id, org_id, role) values ($1, $2, 'owner')`, [ownerId, row.id]);
|
|
87
|
+
await client.query('commit');
|
|
88
|
+
return toOrg(row);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
await client.query('rollback');
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
client.release();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function listOrgs(pool, limit = 100) {
|
|
99
|
+
const result = await pool.query(`select o.id, o.slug, o.name, o.website, o.logo_url, o.description, o.created_at
|
|
100
|
+
from organisations o
|
|
101
|
+
where exists (
|
|
102
|
+
select 1 from jobs j
|
|
103
|
+
where j.org_id = o.id and j.status = 'published'
|
|
104
|
+
and j.published_at is not null and j.published_at <= now()
|
|
105
|
+
and (j.expires_at is null or j.expires_at > now())
|
|
106
|
+
)
|
|
107
|
+
order by o.name asc
|
|
108
|
+
limit $1`, [Math.min(500, Math.max(1, limit))]);
|
|
109
|
+
return result.rows.map(toOrg);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=orgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orgs.js","sourceRoot":"","sources":["../../src/core/orgs.ts"],"names":[],"mappings":"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"}
|