@profullstack/agenticjobs 0.11.0 → 0.14.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 +136 -1
- package/dist/cli/args.js +4 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/index.js +227 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/jobfile.js +38 -2
- package/dist/cli/jobfile.js.map +1 -1
- package/dist/client/client.d.ts +157 -0
- package/dist/client/client.js +44 -0
- package/dist/client/client.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +33 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agentwriter.js +22 -5
- package/dist/core/agentwriter.js.map +1 -1
- package/dist/core/applications.js +1 -1
- package/dist/core/applications.js.map +1 -1
- package/dist/core/candidates.js +3 -5
- package/dist/core/candidates.js.map +1 -1
- package/dist/core/capacity.d.ts +1 -1
- package/dist/core/capacity.js +11 -19
- package/dist/core/capacity.js.map +1 -1
- package/dist/core/coinpay.d.ts +143 -0
- package/dist/core/coinpay.js +417 -0
- package/dist/core/coinpay.js.map +1 -0
- package/dist/core/import.js +19 -9
- package/dist/core/import.js.map +1 -1
- package/dist/core/inbox.d.ts +155 -0
- package/dist/core/inbox.js +353 -0
- package/dist/core/inbox.js.map +1 -0
- package/dist/core/invoices.d.ts +111 -0
- package/dist/core/invoices.js +292 -0
- package/dist/core/invoices.js.map +1 -0
- package/dist/core/jobs.d.ts +24 -6
- package/dist/core/jobs.js +116 -31
- package/dist/core/jobs.js.map +1 -1
- package/dist/core/mail.d.ts +16 -0
- package/dist/core/mail.js +22 -0
- package/dist/core/mail.js.map +1 -1
- package/dist/core/recommendations.d.ts +134 -0
- package/dist/core/recommendations.js +356 -0
- package/dist/core/recommendations.js.map +1 -0
- package/dist/core/updates.d.ts +15 -3
- package/dist/core/updates.js +58 -24
- package/dist/core/updates.js.map +1 -1
- package/dist/core/zip.js +29 -4
- package/dist/core/zip.js.map +1 -1
- package/dist/directory/federate.js +3 -1
- package/dist/directory/federate.js.map +1 -1
- package/dist/directory/fetch.d.ts +1 -1
- package/dist/markup/markdown.js +6 -2
- package/dist/markup/markdown.js.map +1 -1
- package/dist/markup/resume.js +18 -5
- package/dist/markup/resume.js.map +1 -1
- package/dist/mcp/tools.js +249 -7
- package/dist/mcp/tools.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/instance.js +29 -0
- package/dist/schema/instance.js.map +1 -1
- package/dist/schema/job.d.ts +12 -0
- package/dist/schema/job.js +17 -3
- package/dist/schema/job.js.map +1 -1
- package/dist/schema/jsonld.js +7 -6
- package/dist/schema/jsonld.js.map +1 -1
- package/dist/schema/pay.d.ts +173 -0
- package/dist/schema/pay.js +668 -0
- package/dist/schema/pay.js.map +1 -0
- package/dist/server/app.d.ts +4 -3
- package/dist/server/app.js +10 -5
- package/dist/server/app.js.map +1 -1
- package/dist/server/deps.d.ts +5 -0
- package/dist/server/middleware.d.ts +11 -1
- package/dist/server/middleware.js +26 -3
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes/api.js +437 -7
- package/dist/server/routes/api.js.map +1 -1
- package/dist/server/routes/discovery.js +29 -8
- package/dist/server/routes/discovery.js.map +1 -1
- package/dist/server/routes/inbox.d.ts +9 -0
- package/dist/server/routes/inbox.js +223 -0
- package/dist/server/routes/inbox.js.map +1 -0
- package/dist/server/routes/openapi.js +260 -2
- package/dist/server/routes/openapi.js.map +1 -1
- package/dist/server/routes/pages.d.ts +22 -0
- package/dist/server/routes/pages.js +244 -19
- package/dist/server/routes/pages.js.map +1 -1
- package/dist/tui/views.js +9 -3
- package/dist/tui/views.js.map +1 -1
- package/dist/views/candidates.d.ts +7 -0
- package/dist/views/candidates.js +3 -2
- package/dist/views/candidates.js.map +1 -1
- package/dist/views/docs.js +10 -1
- package/dist/views/docs.js.map +1 -1
- package/dist/views/inbox.d.ts +90 -0
- package/dist/views/inbox.js +73 -0
- package/dist/views/inbox.js.map +1 -0
- package/dist/views/jobs.d.ts +25 -0
- package/dist/views/jobs.js +35 -12
- package/dist/views/jobs.js.map +1 -1
- package/dist/views/layout.d.ts +24 -0
- package/dist/views/layout.js +20 -2
- package/dist/views/layout.js.map +1 -1
- package/dist/views/me.d.ts +10 -3
- package/dist/views/me.js +3 -2
- package/dist/views/me.js.map +1 -1
- package/dist/views/post.d.ts +18 -0
- package/dist/views/post.js +39 -7
- package/dist/views/post.js.map +1 -1
- package/dist/views/recommendations.d.ts +43 -0
- package/dist/views/recommendations.js +26 -0
- package/dist/views/recommendations.js.map +1 -0
- package/dist/views/updates.d.ts +13 -0
- package/dist/views/updates.js +6 -5
- package/dist/views/updates.js.map +1 -1
- package/docs/openjob.md +63 -0
- package/migrations/0014_inbox_and_billing.sql +138 -0
- package/migrations/0015_pay_lines.sql +38 -0
- package/migrations/0016_recommendations.sql +65 -0
- package/package.json +1 -1
- package/web/public/app.css +192 -2
- package/web/public/sw.js +1 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { jsx as _jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The inbox pages.
|
|
4
|
+
*
|
|
5
|
+
* Every form here works without JavaScript, like the rest of the site, and
|
|
6
|
+
* every page needs a signed-in person: there is nothing public in an inbox.
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from 'hono';
|
|
9
|
+
import { counterpartyFrom, getThread, listThreads, markRead, notifyParticipants, sendMessage, startThread, } from "../../core/inbox.js";
|
|
10
|
+
import { cancelInvoice, getInvoice, listInvoicesIn, requestPayment, sendInvoice, syncInvoice, } from "../../core/invoices.js";
|
|
11
|
+
import { getAccount } from "../../core/coinpay.js";
|
|
12
|
+
import { getJobBySlug } from "../../core/jobs.js";
|
|
13
|
+
import { getOrgBySlug, listOrgsForUser } from "../../core/orgs.js";
|
|
14
|
+
import { getPublicResume } from "../../core/resumes.js";
|
|
15
|
+
import { toCandidateSummary } from "../../core/candidates.js";
|
|
16
|
+
import { Layout } from "../../views/layout.js";
|
|
17
|
+
import { InboxPage, NewThreadPage, ThreadPage } from "../../views/inbox.js";
|
|
18
|
+
import { formOf, requireViewer, shell } from "./pages.js";
|
|
19
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
|
+
export function inboxRoutes() {
|
|
21
|
+
const inbox = new Hono();
|
|
22
|
+
inbox.get('/inbox', async (c) => {
|
|
23
|
+
const { pool } = c.get('deps');
|
|
24
|
+
const viewer = requireViewer(c);
|
|
25
|
+
if (viewer instanceof Response)
|
|
26
|
+
return viewer;
|
|
27
|
+
const threads = await listThreads(pool, viewer.id);
|
|
28
|
+
return c.html(_jsx(Layout, { ...shell(c), title: "Inbox", noindex: true, children: _jsx(InboxPage, { threads: threads }) }));
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Who the compose page is to, from its query or its form.
|
|
32
|
+
*
|
|
33
|
+
* Resolved to a page the person can see, so the heading can name them, and
|
|
34
|
+
* so "message" on a private resume is a 404 rather than a way to reach
|
|
35
|
+
* somebody who did not publish.
|
|
36
|
+
*/
|
|
37
|
+
async function resolveTarget(c, input) {
|
|
38
|
+
const { pool } = c.get('deps');
|
|
39
|
+
const candidate = (input['candidate'] ?? '').trim();
|
|
40
|
+
const employer = (input['employer'] ?? '').trim();
|
|
41
|
+
if (candidate !== '') {
|
|
42
|
+
const resume = await getPublicResume(pool, candidate);
|
|
43
|
+
if (resume === null)
|
|
44
|
+
return null;
|
|
45
|
+
const summary = toCandidateSummary(resume);
|
|
46
|
+
return {
|
|
47
|
+
to: { kind: 'candidate', name: summary.name, slug: candidate },
|
|
48
|
+
target: { candidate },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (employer !== '') {
|
|
52
|
+
const org = await getOrgBySlug(pool, employer);
|
|
53
|
+
if (org === null)
|
|
54
|
+
return null;
|
|
55
|
+
return { to: { kind: 'employer', name: org.name, slug: org.slug }, target: { employer } };
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const composePage = async (c, input, status = 200, error) => {
|
|
60
|
+
const { pool } = c.get('deps');
|
|
61
|
+
const viewer = requireViewer(c);
|
|
62
|
+
if (viewer instanceof Response)
|
|
63
|
+
return viewer;
|
|
64
|
+
const resolved = await resolveTarget(c, input);
|
|
65
|
+
if (resolved === null)
|
|
66
|
+
return c.notFound();
|
|
67
|
+
const jobSlug = (input['job'] ?? '').trim();
|
|
68
|
+
const job = jobSlug === '' ? null : await getJobBySlug(pool, jobSlug);
|
|
69
|
+
const asOrgs = await listOrgsForUser(pool, viewer.id);
|
|
70
|
+
return c.html(_jsx(Layout, { ...shell(c), title: `Message ${resolved.to.name}`, noindex: true, children: _jsx(NewThreadPage, { to: resolved.to, job: job === null ? null : { slug: job.slug, title: job.title }, asOrgs: asOrgs, values: input, target: resolved.target, ...(error === undefined ? {} : { error }) }) }), status);
|
|
71
|
+
};
|
|
72
|
+
inbox.get('/inbox/new', (c) => {
|
|
73
|
+
const params = new URL(c.req.url).searchParams;
|
|
74
|
+
return composePage(c, Object.fromEntries(params.entries()));
|
|
75
|
+
});
|
|
76
|
+
inbox.post('/inbox/new', async (c) => {
|
|
77
|
+
const { pool, config, mailer } = c.get('deps');
|
|
78
|
+
const viewer = requireViewer(c);
|
|
79
|
+
if (viewer instanceof Response)
|
|
80
|
+
return viewer;
|
|
81
|
+
const form = await formOf(c);
|
|
82
|
+
const to = await counterpartyFrom(pool, {
|
|
83
|
+
candidate: form['candidate'] ?? null,
|
|
84
|
+
employer: form['employer'] ?? null,
|
|
85
|
+
});
|
|
86
|
+
if (to === null)
|
|
87
|
+
return c.notFound();
|
|
88
|
+
const jobSlug = (form['job'] ?? '').trim();
|
|
89
|
+
const job = jobSlug === '' ? null : await getJobBySlug(pool, jobSlug);
|
|
90
|
+
const asSlug = (form['as'] ?? '').trim();
|
|
91
|
+
const asOrg = asSlug === '' ? null : await getOrgBySlug(pool, asSlug);
|
|
92
|
+
if (asSlug !== '' && asOrg === null)
|
|
93
|
+
return composePage(c, form, 400, 'No such employer.');
|
|
94
|
+
const started = await startThread(pool, viewer.id, to, {
|
|
95
|
+
subject: form['subject'],
|
|
96
|
+
body: form['body'],
|
|
97
|
+
jobId: job?.id ?? null,
|
|
98
|
+
as: asOrg?.id ?? null,
|
|
99
|
+
});
|
|
100
|
+
if (typeof started === 'string')
|
|
101
|
+
return composePage(c, form, 400, started);
|
|
102
|
+
await notifyParticipants(pool, {
|
|
103
|
+
mailer,
|
|
104
|
+
boardName: config.boardName,
|
|
105
|
+
publicUrl: config.publicUrl,
|
|
106
|
+
threadId: started.threadId,
|
|
107
|
+
senderId: viewer.id,
|
|
108
|
+
kind: 'text',
|
|
109
|
+
});
|
|
110
|
+
return c.redirect(`/inbox/${started.threadId}`, 303);
|
|
111
|
+
});
|
|
112
|
+
const threadPage = async (c, threadId, status = 200, error, values) => {
|
|
113
|
+
const { pool, coinpay } = c.get('deps');
|
|
114
|
+
const viewer = requireViewer(c);
|
|
115
|
+
if (viewer instanceof Response)
|
|
116
|
+
return viewer;
|
|
117
|
+
if (!UUID.test(threadId))
|
|
118
|
+
return c.notFound();
|
|
119
|
+
const thread = await getThread(pool, threadId, viewer.id);
|
|
120
|
+
if (thread === null)
|
|
121
|
+
return c.notFound();
|
|
122
|
+
// Ask CoinPay about every open quote before rendering, so a payer who paid
|
|
123
|
+
// and came back sees "paid" rather than a stale button.
|
|
124
|
+
const invoices = await Promise.all((await listInvoicesIn(pool, coinpay, threadId)).map((invoice) => syncInvoice(pool, coinpay, invoice)));
|
|
125
|
+
const account = coinpay === null ? null : await getAccount(pool, viewer.id);
|
|
126
|
+
if (status === 200)
|
|
127
|
+
await markRead(pool, threadId, viewer.id);
|
|
128
|
+
return c.html(_jsx(Layout, { ...shell(c), title: thread.subject, noindex: true, children: _jsx(ThreadPage, { thread: thread, invoices: invoices, viewerId: viewer.id, billing: { enabled: coinpay !== null, account }, ...(error === undefined ? {} : { error }), ...(values === undefined ? {} : { values }) }) }), status);
|
|
129
|
+
};
|
|
130
|
+
inbox.get('/inbox/:id', (c) => threadPage(c, c.req.param('id')));
|
|
131
|
+
inbox.post('/inbox/:id', async (c) => {
|
|
132
|
+
const { pool, config, mailer } = c.get('deps');
|
|
133
|
+
const viewer = requireViewer(c);
|
|
134
|
+
if (viewer instanceof Response)
|
|
135
|
+
return viewer;
|
|
136
|
+
const threadId = c.req.param('id');
|
|
137
|
+
if (!UUID.test(threadId))
|
|
138
|
+
return c.notFound();
|
|
139
|
+
const form = await formOf(c);
|
|
140
|
+
const sent = await sendMessage(pool, threadId, viewer.id, form['body']);
|
|
141
|
+
if (typeof sent === 'string')
|
|
142
|
+
return threadPage(c, threadId, 400, sent, form);
|
|
143
|
+
await notifyParticipants(pool, {
|
|
144
|
+
mailer,
|
|
145
|
+
boardName: config.boardName,
|
|
146
|
+
publicUrl: config.publicUrl,
|
|
147
|
+
threadId,
|
|
148
|
+
senderId: viewer.id,
|
|
149
|
+
kind: 'text',
|
|
150
|
+
});
|
|
151
|
+
return c.redirect(`/inbox/${threadId}#${sent.id}`, 303);
|
|
152
|
+
});
|
|
153
|
+
inbox.post('/inbox/:id/invoices', async (c) => {
|
|
154
|
+
const { pool, config, mailer, coinpay } = c.get('deps');
|
|
155
|
+
const viewer = requireViewer(c);
|
|
156
|
+
if (viewer instanceof Response)
|
|
157
|
+
return viewer;
|
|
158
|
+
const threadId = c.req.param('id');
|
|
159
|
+
if (!UUID.test(threadId))
|
|
160
|
+
return c.notFound();
|
|
161
|
+
const form = await formOf(c);
|
|
162
|
+
const invoice = await sendInvoice(pool, coinpay, {
|
|
163
|
+
threadId,
|
|
164
|
+
payeeId: viewer.id,
|
|
165
|
+
amount: form['amount'],
|
|
166
|
+
currency: form['currency'],
|
|
167
|
+
description: form['description'],
|
|
168
|
+
});
|
|
169
|
+
if (typeof invoice === 'string')
|
|
170
|
+
return threadPage(c, threadId, 400, invoice, form);
|
|
171
|
+
await notifyParticipants(pool, {
|
|
172
|
+
mailer,
|
|
173
|
+
boardName: config.boardName,
|
|
174
|
+
publicUrl: config.publicUrl,
|
|
175
|
+
threadId,
|
|
176
|
+
senderId: viewer.id,
|
|
177
|
+
kind: 'invoice',
|
|
178
|
+
});
|
|
179
|
+
return c.redirect(`/inbox/${threadId}`, 303);
|
|
180
|
+
});
|
|
181
|
+
/** Off to CoinPay with a live quote. */
|
|
182
|
+
inbox.post('/inbox/:id/invoices/:invoiceId/pay', async (c) => {
|
|
183
|
+
const { pool, config, coinpay } = c.get('deps');
|
|
184
|
+
const viewer = requireViewer(c);
|
|
185
|
+
if (viewer instanceof Response)
|
|
186
|
+
return viewer;
|
|
187
|
+
const threadId = c.req.param('id');
|
|
188
|
+
const invoiceId = c.req.param('invoiceId');
|
|
189
|
+
if (!UUID.test(threadId) || !UUID.test(invoiceId))
|
|
190
|
+
return c.notFound();
|
|
191
|
+
const result = await requestPayment(pool, coinpay, {
|
|
192
|
+
invoiceId,
|
|
193
|
+
payerId: viewer.id,
|
|
194
|
+
publicUrl: config.publicUrl,
|
|
195
|
+
});
|
|
196
|
+
if (typeof result === 'string')
|
|
197
|
+
return threadPage(c, threadId, 400, result);
|
|
198
|
+
if (result.status === 'paid' || result.payment === null) {
|
|
199
|
+
return c.redirect(`/inbox/${threadId}`, 303);
|
|
200
|
+
}
|
|
201
|
+
return c.redirect(result.payment.url, 303);
|
|
202
|
+
});
|
|
203
|
+
inbox.post('/inbox/:id/invoices/:invoiceId/cancel', async (c) => {
|
|
204
|
+
const { pool, coinpay } = c.get('deps');
|
|
205
|
+
const viewer = requireViewer(c);
|
|
206
|
+
if (viewer instanceof Response)
|
|
207
|
+
return viewer;
|
|
208
|
+
const threadId = c.req.param('id');
|
|
209
|
+
const invoiceId = c.req.param('invoiceId');
|
|
210
|
+
if (!UUID.test(threadId) || !UUID.test(invoiceId))
|
|
211
|
+
return c.notFound();
|
|
212
|
+
const invoice = await getInvoice(pool, coinpay, invoiceId, viewer.id);
|
|
213
|
+
if (invoice === null || invoice.threadId !== threadId)
|
|
214
|
+
return c.notFound();
|
|
215
|
+
const cancelled = await cancelInvoice(pool, invoiceId, viewer.id);
|
|
216
|
+
if (!cancelled) {
|
|
217
|
+
return threadPage(c, threadId, 400, 'Only the sender can cancel an invoice, and only while it is unpaid.');
|
|
218
|
+
}
|
|
219
|
+
return c.redirect(`/inbox/${threadId}`, 303);
|
|
220
|
+
});
|
|
221
|
+
return inbox;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../../src/server/routes/inbox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -37,6 +37,18 @@ export function openApiDocument(config) {
|
|
|
37
37
|
{ name: 'candidates', description: 'People who published a resume here.' },
|
|
38
38
|
{ name: 'employers', description: 'The organisations a listing belongs to.' },
|
|
39
39
|
{ name: 'updates', description: 'Short posts from employers and candidates, and following them.' },
|
|
40
|
+
{
|
|
41
|
+
name: 'recommendations',
|
|
42
|
+
description: 'What somebody who worked with you says about you, on your page once you approve it. Not a rating: a paragraph with a name on it, in either direction.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'inbox',
|
|
46
|
+
description: 'Private conversations. The only way to reach somebody here; there is no public commenting.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'billing',
|
|
50
|
+
description: "Invoices sent through the inbox, settled on CoinPay to the payee's own wallet.",
|
|
51
|
+
},
|
|
40
52
|
{ name: 'auth', description: 'Device flow for terminals, magic links for browsers.' },
|
|
41
53
|
{ name: 'federation', description: 'The directory of instances, and search across them.' },
|
|
42
54
|
],
|
|
@@ -65,8 +77,34 @@ export function openApiDocument(config) {
|
|
|
65
77
|
post: {
|
|
66
78
|
tags: ['jobs'],
|
|
67
79
|
summary: 'Post a job. Creates a draft unless publish is true.',
|
|
80
|
+
description: 'Pay is required to publish. Send `pay` as an array of lines written the way a person says them ("$120k - $150k a year", "$0.25 per task", "$5000 fixed", "10% revenue share", "0.01 SOL per task"), or as objects with type, min, max, currency and unit; `payMethod` says how it is settled (SOL, USDC, bank transfer, PayPal); `unpaid: true` says the role pays nothing. With `publish: true` and no pay the request is refused with `pay_required` and nothing is created.',
|
|
68
81
|
security: [{ bearer: [] }],
|
|
69
|
-
|
|
82
|
+
requestBody: {
|
|
83
|
+
required: true,
|
|
84
|
+
content: {
|
|
85
|
+
'application/json': {
|
|
86
|
+
schema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
required: ['org', 'title', 'description'],
|
|
89
|
+
properties: {
|
|
90
|
+
org: { type: 'string' },
|
|
91
|
+
title: { type: 'string' },
|
|
92
|
+
description: { type: 'string', description: 'Markdown.' },
|
|
93
|
+
pay: {
|
|
94
|
+
type: 'array',
|
|
95
|
+
items: { oneOf: [{ type: 'string' }, { $ref: '#/components/schemas/PayLine' }] },
|
|
96
|
+
},
|
|
97
|
+
payMethod: { type: 'string' },
|
|
98
|
+
payEquity: { type: 'string' },
|
|
99
|
+
unpaid: { type: 'boolean' },
|
|
100
|
+
agentPolicy: { type: 'string', enum: ['welcome', 'disclose', 'human-only'] },
|
|
101
|
+
publish: { type: 'boolean' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
responses: { 201: ok('The job as stored.'), 400: err(), 401: err(), 403: err() },
|
|
70
108
|
},
|
|
71
109
|
},
|
|
72
110
|
'/api/v1/jobs/{slug}': {
|
|
@@ -321,6 +359,122 @@ export function openApiDocument(config) {
|
|
|
321
359
|
responses: { 200: ok('Employers and candidates you follow.'), 401: err() },
|
|
322
360
|
},
|
|
323
361
|
},
|
|
362
|
+
'/api/v1/inbox': {
|
|
363
|
+
get: {
|
|
364
|
+
tags: ['inbox'],
|
|
365
|
+
summary: 'Your conversations, newest activity first, with unread counts.',
|
|
366
|
+
security: [{ bearer: [] }],
|
|
367
|
+
responses: { 200: ok('Threads.'), 401: err() },
|
|
368
|
+
},
|
|
369
|
+
post: {
|
|
370
|
+
tags: ['inbox'],
|
|
371
|
+
summary: 'Write to a candidate or an employer. Continues the conversation you already have with them.',
|
|
372
|
+
description: 'Send {"candidate": slug} or {"employer": slug}, a "body", and optionally a "subject", a "job" slug the message is about, and "as": an employer slug to write on behalf of (you must belong to it). The same two parties about the same job is one conversation, so a second POST lands as a new message in it and answers 200 rather than 201. Twenty new conversations a day per account. The other side is emailed that there is a message, never the message itself. There is no public commenting on this board: this is how people are reached.',
|
|
373
|
+
security: [{ bearer: [] }],
|
|
374
|
+
responses: {
|
|
375
|
+
200: ok('Added to the existing conversation: threadId, messageId, url.'),
|
|
376
|
+
201: ok('A new conversation: threadId, messageId, url.'),
|
|
377
|
+
400: err(),
|
|
378
|
+
401: err(),
|
|
379
|
+
404: err(),
|
|
380
|
+
429: err(),
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
'/api/v1/inbox/{id}': {
|
|
385
|
+
get: {
|
|
386
|
+
tags: ['inbox'],
|
|
387
|
+
summary: 'One conversation: its messages and the invoices in it. Opening it marks it read.',
|
|
388
|
+
security: [{ bearer: [] }],
|
|
389
|
+
parameters: [pathParam('id')],
|
|
390
|
+
responses: { 200: ok('The thread and its invoices.'), 401: err(), 404: err() },
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
'/api/v1/inbox/{id}/messages': {
|
|
394
|
+
post: {
|
|
395
|
+
tags: ['inbox'],
|
|
396
|
+
summary: 'Reply in a conversation you are in.',
|
|
397
|
+
security: [{ bearer: [] }],
|
|
398
|
+
parameters: [pathParam('id')],
|
|
399
|
+
responses: { 201: ok('The message.'), 400: err(), 401: err(), 404: err() },
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
'/api/v1/inbox/{id}/invoices': {
|
|
403
|
+
post: {
|
|
404
|
+
tags: ['billing'],
|
|
405
|
+
summary: 'Send an invoice into a conversation. You are the payee.',
|
|
406
|
+
description: 'Send "amount" in US dollars, "description", and "currency": a chain you hold a wallet for on your connected CoinPay account (BTC, ETH, SOL, USDC_POL ...). Omit currency when you have exactly one wallet. The payment settles on CoinPay straight to that wallet; this board never holds it. Needs a connected CoinPay account with wallet:read, which is done in a browser at /me/coinpay/connect.',
|
|
407
|
+
security: [{ bearer: [] }],
|
|
408
|
+
parameters: [pathParam('id')],
|
|
409
|
+
responses: { 201: ok('The invoice.'), 400: err(), 401: err(), 404: err() },
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
'/api/v1/invoices': {
|
|
413
|
+
get: {
|
|
414
|
+
tags: ['billing'],
|
|
415
|
+
summary: 'Every invoice you sent or can pay, newest first.',
|
|
416
|
+
security: [{ bearer: [] }],
|
|
417
|
+
responses: { 200: ok('Invoices.'), 401: err() },
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
'/api/v1/invoices/{id}': {
|
|
421
|
+
get: {
|
|
422
|
+
tags: ['billing'],
|
|
423
|
+
summary: 'One invoice, with its payment state checked against CoinPay.',
|
|
424
|
+
security: [{ bearer: [] }],
|
|
425
|
+
parameters: [pathParam('id')],
|
|
426
|
+
responses: { 200: ok('The invoice.'), 401: err(), 404: err() },
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
'/api/v1/invoices/{id}/pay': {
|
|
430
|
+
post: {
|
|
431
|
+
tags: ['billing'],
|
|
432
|
+
summary: 'Get a live quote to pay an invoice you were sent.',
|
|
433
|
+
description: 'Returns the invoice with "payment" filled in: "url" is the CoinPay page to pay on, "address" and "amountCrypto" let a wallet pay directly. A quote lasts a few minutes; call again for a fresh one. Paid is reported by the webhook and by reading the invoice.',
|
|
434
|
+
security: [{ bearer: [] }],
|
|
435
|
+
parameters: [pathParam('id')],
|
|
436
|
+
responses: { 200: ok('The invoice with a payment to make.'), 400: err(), 401: err(), 404: err() },
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
'/api/v1/invoices/{id}/cancel': {
|
|
440
|
+
post: {
|
|
441
|
+
tags: ['billing'],
|
|
442
|
+
summary: 'Take back an unpaid invoice you sent.',
|
|
443
|
+
security: [{ bearer: [] }],
|
|
444
|
+
parameters: [pathParam('id')],
|
|
445
|
+
responses: { 200: ok('Cancelled.'), 401: err(), 404: err(), 409: err() },
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
'/api/v1/coinpay': {
|
|
449
|
+
get: {
|
|
450
|
+
tags: ['billing'],
|
|
451
|
+
summary: 'Whether this board has billing, and whether your CoinPay account is connected.',
|
|
452
|
+
description: '"configured" is the board; "account" is you, with "usable" saying whether the connection can read your wallets (a connection without the wallet:read scope shows here as not usable and has to be reconnected). Connecting is a browser step at "connectUrl".',
|
|
453
|
+
security: [{ bearer: [] }],
|
|
454
|
+
responses: { 200: ok('Connection state.'), 401: err() },
|
|
455
|
+
},
|
|
456
|
+
delete: {
|
|
457
|
+
tags: ['billing'],
|
|
458
|
+
summary: 'Disconnect your CoinPay account. Invoices already sent keep their wallet.',
|
|
459
|
+
security: [{ bearer: [] }],
|
|
460
|
+
responses: { 200: ok('Disconnected.'), 401: err() },
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
'/api/v1/coinpay/callback': {
|
|
464
|
+
get: {
|
|
465
|
+
tags: ['billing'],
|
|
466
|
+
summary: 'Where CoinPay sends a browser back after consent. Not for calling directly.',
|
|
467
|
+
parameters: [param('code', 'From CoinPay.'), param('state', 'From CoinPay.')],
|
|
468
|
+
responses: { 303: { description: 'Back to the board.' }, 400: err(), 404: err() },
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
'/api/v1/coinpay/webhook': {
|
|
472
|
+
post: {
|
|
473
|
+
tags: ['billing'],
|
|
474
|
+
summary: 'CoinPay reporting a settled payment. Signed with the business webhook secret.',
|
|
475
|
+
responses: { 200: ok('Received.'), 401: err(), 404: err() },
|
|
476
|
+
},
|
|
477
|
+
},
|
|
324
478
|
'/api/v1/jobs/import': {
|
|
325
479
|
post: {
|
|
326
480
|
tags: ['jobs'],
|
|
@@ -386,6 +540,7 @@ export function openApiDocument(config) {
|
|
|
386
540
|
post: {
|
|
387
541
|
tags: ['jobs'],
|
|
388
542
|
summary: 'publish, close or reopen a listing.',
|
|
543
|
+
description: 'Publishing requires the listing to say what it pays: at least one pay line with an amount, or unpaid. Otherwise 400 with code pay_required, and the listing stays as it was.',
|
|
389
544
|
security: [{ bearer: [] }],
|
|
390
545
|
parameters: [
|
|
391
546
|
pathParam('slug'),
|
|
@@ -396,7 +551,76 @@ export function openApiDocument(config) {
|
|
|
396
551
|
schema: { type: 'string', enum: ['publish', 'close', 'reopen'] },
|
|
397
552
|
},
|
|
398
553
|
],
|
|
399
|
-
responses: { 200: ok('The listing.'), 401: err(), 403: err(), 404: err() },
|
|
554
|
+
responses: { 200: ok('The listing.'), 400: err(), 401: err(), 403: err(), 404: err() },
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
'/api/v1/candidates/{slug}/recommendations': {
|
|
558
|
+
get: {
|
|
559
|
+
tags: ['recommendations'],
|
|
560
|
+
summary: "The approved recommendations on a candidate's page.",
|
|
561
|
+
parameters: [pathParam('slug')],
|
|
562
|
+
responses: { 200: ok('Recommendations.'), 404: err() },
|
|
563
|
+
},
|
|
564
|
+
post: {
|
|
565
|
+
tags: ['recommendations'],
|
|
566
|
+
summary: 'Recommend a candidate.',
|
|
567
|
+
description: 'As yourself, which needs a published resume so it is signed by a page, or as an employer you post for ("as"). Pending until the candidate approves it; writing again replaces it. Ten a day.',
|
|
568
|
+
security: [{ bearer: [] }],
|
|
569
|
+
parameters: [pathParam('slug')],
|
|
570
|
+
requestBody: {
|
|
571
|
+
required: true,
|
|
572
|
+
content: {
|
|
573
|
+
'application/json': {
|
|
574
|
+
schema: {
|
|
575
|
+
type: 'object',
|
|
576
|
+
required: ['body'],
|
|
577
|
+
properties: {
|
|
578
|
+
body: { type: 'string', minLength: 20, maxLength: 2000 },
|
|
579
|
+
relationship: { type: 'string', maxLength: 120, description: '"Hired them for a three-month contract".' },
|
|
580
|
+
as: { type: 'string', description: 'An employer slug you post for.' },
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
responses: { 201: ok('The recommendation, pending.'), 400: err(), 401: err(), 403: err(), 404: err(), 429: err() },
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
'/api/v1/orgs/{slug}/recommendations': {
|
|
590
|
+
get: {
|
|
591
|
+
tags: ['recommendations'],
|
|
592
|
+
summary: "The approved recommendations on an employer's page.",
|
|
593
|
+
parameters: [pathParam('slug')],
|
|
594
|
+
responses: { 200: ok('Recommendations.'), 404: err() },
|
|
595
|
+
},
|
|
596
|
+
post: {
|
|
597
|
+
tags: ['recommendations'],
|
|
598
|
+
summary: 'Recommend an employer.',
|
|
599
|
+
description: 'The same rules as recommending a candidate. Members cannot recommend their own employer.',
|
|
600
|
+
security: [{ bearer: [] }],
|
|
601
|
+
parameters: [pathParam('slug')],
|
|
602
|
+
responses: { 201: ok('The recommendation, pending.'), 400: err(), 401: err(), 403: err(), 404: err(), 429: err() },
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
'/api/v1/me/recommendations': {
|
|
606
|
+
get: {
|
|
607
|
+
tags: ['recommendations'],
|
|
608
|
+
summary: 'About you and your employers, every status, and what you wrote.',
|
|
609
|
+
security: [{ bearer: [] }],
|
|
610
|
+
responses: { 200: ok('received, given, and how many are pending.'), 401: err() },
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
'/api/v1/recommendations/{id}/{action}': {
|
|
614
|
+
post: {
|
|
615
|
+
tags: ['recommendations'],
|
|
616
|
+
summary: 'approve or reject one written about you, or withdraw one you wrote.',
|
|
617
|
+
description: 'The subject decides, and can change their mind later: an approved one can be rejected, which takes it off the page. Withdraw deletes one you wrote.',
|
|
618
|
+
security: [{ bearer: [] }],
|
|
619
|
+
parameters: [
|
|
620
|
+
pathParam('id'),
|
|
621
|
+
{ name: 'action', in: 'path', required: true, schema: { type: 'string', enum: ['approve', 'reject', 'withdraw'] } },
|
|
622
|
+
],
|
|
623
|
+
responses: { 200: ok('The recommendation, or withdrawn: true.'), 401: err(), 404: err() },
|
|
400
624
|
},
|
|
401
625
|
},
|
|
402
626
|
'/api/v1/auth/magic-link': {
|
|
@@ -505,10 +729,44 @@ export function openApiDocument(config) {
|
|
|
505
729
|
description: 'Where the employer stands on applications written with an agent. Required on every listing, because the alternative is finding out by silent rejection.',
|
|
506
730
|
},
|
|
507
731
|
apply: { type: 'object' },
|
|
732
|
+
pay: { $ref: '#/components/schemas/Pay' },
|
|
733
|
+
salary: {
|
|
734
|
+
type: 'object',
|
|
735
|
+
description: 'The first time-based pay line, flattened: min, max, currency, period, equity, unpaid. Kept for readers written before `pay` existed and for the salary filter and sort. A listing that pays per task has a null range here.',
|
|
736
|
+
},
|
|
508
737
|
tags: { type: 'array', items: { type: 'string' } },
|
|
509
738
|
stack: { type: 'array', items: { type: 'string' } },
|
|
510
739
|
},
|
|
511
740
|
},
|
|
741
|
+
Pay: {
|
|
742
|
+
type: 'object',
|
|
743
|
+
description: 'What a listing pays, in full. Required to publish: at least one line with an amount, or unpaid.',
|
|
744
|
+
properties: {
|
|
745
|
+
lines: { type: 'array', items: { $ref: '#/components/schemas/PayLine' } },
|
|
746
|
+
method: {
|
|
747
|
+
type: 'string',
|
|
748
|
+
nullable: true,
|
|
749
|
+
description: 'How it is settled: a coin (SOL, USDC, ETH, USDT, POL) or a rail (bank transfer, PayPal, payroll).',
|
|
750
|
+
},
|
|
751
|
+
equity: { type: 'string', nullable: true },
|
|
752
|
+
unpaid: { type: 'boolean' },
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
PayLine: {
|
|
756
|
+
type: 'object',
|
|
757
|
+
description: 'One price. The same vocabulary as a ugig.net gig budget: a type, a range, what it is denominated in, and for per_task and per_unit what one unit is.',
|
|
758
|
+
required: ['type', 'currency'],
|
|
759
|
+
properties: {
|
|
760
|
+
type: {
|
|
761
|
+
type: 'string',
|
|
762
|
+
enum: ['hourly', 'daily', 'weekly', 'monthly', 'yearly', 'fixed', 'per_task', 'per_unit', 'revenue_share', 'bounty'],
|
|
763
|
+
},
|
|
764
|
+
min: { type: 'number', nullable: true },
|
|
765
|
+
max: { type: 'number', nullable: true },
|
|
766
|
+
currency: { type: 'string', description: 'USD, EUR, or a ticker such as SOL. "%" for a revenue share.' },
|
|
767
|
+
unit: { type: 'string', nullable: true, description: 'For per_task and per_unit: "task", "PR that fixes a bug you find".' },
|
|
768
|
+
},
|
|
769
|
+
},
|
|
512
770
|
},
|
|
513
771
|
},
|
|
514
772
|
};
|