@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,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invoices: a message with money attached.
|
|
3
|
+
*
|
|
4
|
+
* The payee writes one in a conversation they are in. The other side sees it
|
|
5
|
+
* there, presses Pay, and is sent to CoinPay to settle it in the coin the
|
|
6
|
+
* payee chose, straight to the payee's wallet. The board mints the payment
|
|
7
|
+
* under its own business key and names the payee's address as the payout, so
|
|
8
|
+
* at no point does money sit anywhere the board controls.
|
|
9
|
+
*
|
|
10
|
+
* A CoinPay payment is a quote that expires. The invoice does not: when a
|
|
11
|
+
* quote lapses unpaid, Pay mints a fresh one under the same invoice, and
|
|
12
|
+
* `status` only ever moves sent -> paid or sent -> cancelled. Payment is
|
|
13
|
+
* confirmed by webhook, and by asking CoinPay when the page is opened, so a
|
|
14
|
+
* lost webhook delays the answer rather than losing it.
|
|
15
|
+
*/
|
|
16
|
+
import { clean } from "../schema/text.js";
|
|
17
|
+
import { CoinPayProblem, getAccount } from "./coinpay.js";
|
|
18
|
+
const SELECT = `
|
|
19
|
+
select i.*, u.name as payee_name, u.email as payee_email
|
|
20
|
+
from invoices i join users u on u.id = i.payee_id`;
|
|
21
|
+
function toInvoice(row, coinpay) {
|
|
22
|
+
const name = (row.payee_name ?? '').trim();
|
|
23
|
+
return {
|
|
24
|
+
id: row.id,
|
|
25
|
+
threadId: row.thread_id,
|
|
26
|
+
payee: { id: row.payee_id, name: name === '' ? row.payee_email : name },
|
|
27
|
+
amountUsd: Number(row.amount_usd).toFixed(2),
|
|
28
|
+
currency: row.currency,
|
|
29
|
+
walletAddress: row.wallet_address,
|
|
30
|
+
description: row.description,
|
|
31
|
+
status: row.status,
|
|
32
|
+
payment: row.coinpay_payment_id === null || coinpay === null
|
|
33
|
+
? null
|
|
34
|
+
: {
|
|
35
|
+
id: row.coinpay_payment_id,
|
|
36
|
+
address: row.payment_address,
|
|
37
|
+
amountCrypto: row.amount_crypto,
|
|
38
|
+
expiresAt: row.payment_expires_at,
|
|
39
|
+
url: coinpay.payUrl(row.coinpay_payment_id),
|
|
40
|
+
},
|
|
41
|
+
paidAt: row.paid_at,
|
|
42
|
+
paidBy: row.paid_by,
|
|
43
|
+
txHash: row.tx_hash,
|
|
44
|
+
createdAt: row.created_at,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** A dollar amount as typed: "120", "120.50", "$1,200". Null when it is not one. */
|
|
48
|
+
export function parseAmount(value) {
|
|
49
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
50
|
+
return null;
|
|
51
|
+
const raw = String(value).replace(/[$,\s]/g, '');
|
|
52
|
+
if (!/^\d+(\.\d{1,2})?$/.test(raw))
|
|
53
|
+
return null;
|
|
54
|
+
const amount = Number(raw);
|
|
55
|
+
if (!(amount > 0) || amount > 1_000_000)
|
|
56
|
+
return null;
|
|
57
|
+
return amount.toFixed(2);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Send one. Returns the invoice, or a sentence saying why not.
|
|
61
|
+
*
|
|
62
|
+
* The payee has to be in the conversation and has to have connected a CoinPay
|
|
63
|
+
* account with a wallet on the chain they chose; the address is copied onto
|
|
64
|
+
* the invoice at this moment and never read again, so what the payer pays to
|
|
65
|
+
* is what the payee had connected when they asked.
|
|
66
|
+
*/
|
|
67
|
+
export async function sendInvoice(pool, coinpay, input) {
|
|
68
|
+
if (coinpay === null)
|
|
69
|
+
return 'This board has no payment rail configured.';
|
|
70
|
+
const amount = parseAmount(input.amount);
|
|
71
|
+
if (amount === null)
|
|
72
|
+
return 'The amount has to be a number of dollars, like 1200 or 1200.50.';
|
|
73
|
+
const description = clean(input.description, 1000, { multiline: true });
|
|
74
|
+
const account = await getAccount(pool, input.payeeId);
|
|
75
|
+
if (account === null)
|
|
76
|
+
return 'Connect a CoinPay account first, so there is a wallet to be paid to.';
|
|
77
|
+
if (!account.usable)
|
|
78
|
+
return 'Your CoinPay connection has lapsed. Reconnect it, then send the invoice.';
|
|
79
|
+
if (account.wallets.length === 0) {
|
|
80
|
+
return 'Your CoinPay account has no wallet yet. Add one on CoinPay, refresh the connection here, then send the invoice.';
|
|
81
|
+
}
|
|
82
|
+
const wanted = clean(input.currency, 20).toUpperCase();
|
|
83
|
+
const wallet = wanted === ''
|
|
84
|
+
? account.wallets.length === 1
|
|
85
|
+
? account.wallets[0]
|
|
86
|
+
: undefined
|
|
87
|
+
: account.wallets.find((w) => w.chain === wanted);
|
|
88
|
+
if (wallet === undefined) {
|
|
89
|
+
return wanted === ''
|
|
90
|
+
? `Say which coin to be paid in: ${account.wallets.map((w) => w.chain).join(', ')}.`
|
|
91
|
+
: `You have no ${wanted} wallet connected. You can be paid in: ${account.wallets.map((w) => w.chain).join(', ')}.`;
|
|
92
|
+
}
|
|
93
|
+
const client = await pool.connect();
|
|
94
|
+
try {
|
|
95
|
+
await client.query('begin');
|
|
96
|
+
const member = await client.query(`select 1 from thread_participants where thread_id = $1 and user_id = $2`, [input.threadId, input.payeeId]);
|
|
97
|
+
if (member.rows.length === 0) {
|
|
98
|
+
await client.query('rollback');
|
|
99
|
+
return 'You are not in that conversation.';
|
|
100
|
+
}
|
|
101
|
+
const inserted = await client.query(`insert into invoices (thread_id, payee_id, amount_usd, currency, wallet_address, description)
|
|
102
|
+
values ($1, $2, $3, $4, $5, $6) returning id`, [input.threadId, input.payeeId, amount, wallet.chain, wallet.address, description]);
|
|
103
|
+
const invoiceId = inserted.rows[0].id;
|
|
104
|
+
const body = description === '' ? `Invoice for $${amount}` : description;
|
|
105
|
+
const message = await client.query(`insert into messages (thread_id, sender_id, kind, body, invoice_id)
|
|
106
|
+
values ($1, $2, 'invoice', $3, $4) returning created_at`, [input.threadId, input.payeeId, body, invoiceId]);
|
|
107
|
+
await client.query(`update threads set last_message_at = $2 where id = $1`, [
|
|
108
|
+
input.threadId,
|
|
109
|
+
message.rows[0].created_at,
|
|
110
|
+
]);
|
|
111
|
+
await client.query(`update thread_participants set last_read_at = $3 where thread_id = $1 and user_id = $2`, [input.threadId, input.payeeId, message.rows[0].created_at]);
|
|
112
|
+
await client.query('commit');
|
|
113
|
+
const row = await pool.query(`${SELECT} where i.id = $1`, [invoiceId]);
|
|
114
|
+
return toInvoice(row.rows[0], coinpay);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
await client.query('rollback');
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
client.release();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** The invoices in a conversation, oldest first, for rendering beside messages. */
|
|
125
|
+
export async function listInvoicesIn(pool, coinpay, threadId) {
|
|
126
|
+
const result = await pool.query(`${SELECT} where i.thread_id = $1 order by i.created_at asc`, [threadId]);
|
|
127
|
+
return result.rows.map((row) => toInvoice(row, coinpay));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Everything the viewer sent or can pay: their invoices, and the invoices in
|
|
131
|
+
* conversations they are in. Newest first.
|
|
132
|
+
*/
|
|
133
|
+
export async function listInvoicesFor(pool, coinpay, viewerId) {
|
|
134
|
+
const result = await pool.query(`${SELECT}
|
|
135
|
+
where exists (select 1 from thread_participants tp
|
|
136
|
+
where tp.thread_id = i.thread_id and tp.user_id = $1)
|
|
137
|
+
order by i.created_at desc limit 200`, [viewerId]);
|
|
138
|
+
return result.rows.map((row) => toInvoice(row, coinpay));
|
|
139
|
+
}
|
|
140
|
+
/** One invoice, for a participant of its conversation. Null for anybody else. */
|
|
141
|
+
export async function getInvoice(pool, coinpay, invoiceId, viewerId) {
|
|
142
|
+
const result = await pool.query(`${SELECT}
|
|
143
|
+
where i.id = $1
|
|
144
|
+
and exists (select 1 from thread_participants tp
|
|
145
|
+
where tp.thread_id = i.thread_id and tp.user_id = $2)`, [invoiceId, viewerId]);
|
|
146
|
+
const row = result.rows[0];
|
|
147
|
+
return row === undefined ? null : toInvoice(row, coinpay);
|
|
148
|
+
}
|
|
149
|
+
/** The payee takes it back. Only while unpaid. */
|
|
150
|
+
export async function cancelInvoice(pool, invoiceId, payeeId) {
|
|
151
|
+
const result = await pool.query(`update invoices set status = 'cancelled', updated_at = now()
|
|
152
|
+
where id = $1 and payee_id = $2 and status = 'sent'`, [invoiceId, payeeId]);
|
|
153
|
+
return (result.rowCount ?? 0) > 0;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get somewhere to pay. Returns the invoice with a live payment on it, or a
|
|
157
|
+
* sentence.
|
|
158
|
+
*
|
|
159
|
+
* Anyone in the conversation but the payee may pay. A payment still inside its
|
|
160
|
+
* quote window is reused; otherwise a new one is minted for the same invoice,
|
|
161
|
+
* and the id is remembered so the webhook can find its way back.
|
|
162
|
+
*/
|
|
163
|
+
export async function requestPayment(pool, coinpay, input) {
|
|
164
|
+
if (coinpay === null)
|
|
165
|
+
return 'This board has no payment rail configured.';
|
|
166
|
+
const current = await getInvoice(pool, coinpay, input.invoiceId, input.payerId);
|
|
167
|
+
if (current === null)
|
|
168
|
+
return 'No such invoice.';
|
|
169
|
+
if (current.payee.id === input.payerId)
|
|
170
|
+
return 'You cannot pay your own invoice.';
|
|
171
|
+
if (current.status === 'paid')
|
|
172
|
+
return 'That invoice is already paid.';
|
|
173
|
+
if (current.status === 'cancelled')
|
|
174
|
+
return 'That invoice was cancelled.';
|
|
175
|
+
// Ask CoinPay about the payment we have before minting another: a payer who
|
|
176
|
+
// paid and came back should see "paid", not a second quote.
|
|
177
|
+
const synced = await syncInvoice(pool, coinpay, current);
|
|
178
|
+
if (synced.status === 'paid')
|
|
179
|
+
return synced;
|
|
180
|
+
if (synced.payment !== null &&
|
|
181
|
+
synced.payment.expiresAt !== null &&
|
|
182
|
+
new Date(synced.payment.expiresAt).getTime() > Date.now() + 60_000) {
|
|
183
|
+
return synced;
|
|
184
|
+
}
|
|
185
|
+
let minted;
|
|
186
|
+
try {
|
|
187
|
+
minted = await coinpay.createPayment({
|
|
188
|
+
amountUsd: current.amountUsd,
|
|
189
|
+
chain: current.currency,
|
|
190
|
+
payeeAddress: current.walletAddress,
|
|
191
|
+
description: current.description === '' ? `Invoice ${current.id.slice(0, 8)}` : current.description,
|
|
192
|
+
redirectUrl: `${input.publicUrl}/inbox/${current.threadId}`,
|
|
193
|
+
// One quote per invoice per minute: a double click is one payment.
|
|
194
|
+
idempotencyKey: `${current.id}:${Math.floor(Date.now() / 60_000)}`,
|
|
195
|
+
metadata: {
|
|
196
|
+
source: 'agenticjobs',
|
|
197
|
+
invoice_id: current.id,
|
|
198
|
+
thread_id: current.threadId,
|
|
199
|
+
payer_id: input.payerId,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
if (error instanceof CoinPayProblem)
|
|
205
|
+
return error.message;
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
// Two statements, not one with a CTE: a data-modifying CTE is not visible to
|
|
209
|
+
// the SELECT beside it, which would read the row from before the update and
|
|
210
|
+
// hand back an invoice with no payment on it.
|
|
211
|
+
const updated = await pool.query(`update invoices set coinpay_payment_id = $2, payment_address = $3, amount_crypto = $4,
|
|
212
|
+
payment_expires_at = $5, updated_at = now()
|
|
213
|
+
where id = $1 and status = 'sent'`, [
|
|
214
|
+
current.id,
|
|
215
|
+
minted.id,
|
|
216
|
+
minted.address,
|
|
217
|
+
minted.amountCrypto,
|
|
218
|
+
minted.expiresAt ?? new Date(Date.now() + 15 * 60_000).toISOString(),
|
|
219
|
+
]);
|
|
220
|
+
if ((updated.rowCount ?? 0) === 0)
|
|
221
|
+
return 'That invoice changed while the payment was being created.';
|
|
222
|
+
const saved = await pool.query(`${SELECT} where i.id = $1`, [current.id]);
|
|
223
|
+
const row = saved.rows[0];
|
|
224
|
+
return row === undefined ? 'No such invoice.' : toInvoice(row, coinpay);
|
|
225
|
+
}
|
|
226
|
+
const PAID = new Set(['confirmed', 'forwarded', 'completed', 'paid']);
|
|
227
|
+
const DEAD = new Set(['expired', 'failed', 'cancelled']);
|
|
228
|
+
/**
|
|
229
|
+
* Ask CoinPay how the current payment is doing and record the answer.
|
|
230
|
+
*
|
|
231
|
+
* Paid is paid, whichever way the news arrives. A dead quote is cleared so the
|
|
232
|
+
* next Pay mints a fresh one. Anything in between, or CoinPay not answering,
|
|
233
|
+
* leaves the invoice as it was: this is a read that happens on page load, and
|
|
234
|
+
* a provider hiccup must not turn into a changed status.
|
|
235
|
+
*/
|
|
236
|
+
export async function syncInvoice(pool, coinpay, invoice) {
|
|
237
|
+
if (coinpay === null || invoice.status !== 'sent' || invoice.payment === null)
|
|
238
|
+
return invoice;
|
|
239
|
+
let payment;
|
|
240
|
+
try {
|
|
241
|
+
payment = await coinpay.getPayment(invoice.payment.id);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return invoice;
|
|
245
|
+
}
|
|
246
|
+
if (payment === null)
|
|
247
|
+
return invoice;
|
|
248
|
+
if (PAID.has(payment.status)) {
|
|
249
|
+
await markPaid(pool, { paymentId: payment.id, txHash: payment.txHash });
|
|
250
|
+
}
|
|
251
|
+
else if (DEAD.has(payment.status)) {
|
|
252
|
+
await pool.query(`update invoices set coinpay_payment_id = null, payment_address = null, amount_crypto = null,
|
|
253
|
+
payment_expires_at = null, updated_at = now()
|
|
254
|
+
where id = $1 and coinpay_payment_id = $2 and status = 'sent'`, [invoice.id, payment.id]);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
return invoice;
|
|
258
|
+
}
|
|
259
|
+
const fresh = await pool.query(`${SELECT} where i.id = $1`, [invoice.id]);
|
|
260
|
+
const row = fresh.rows[0];
|
|
261
|
+
return row === undefined ? invoice : toInvoice(row, coinpay);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Record a payment as settled. Idempotent: the webhook fires for `confirmed`
|
|
265
|
+
* and again for `forwarded`, and a poll may land between them.
|
|
266
|
+
*/
|
|
267
|
+
export async function markPaid(pool, input) {
|
|
268
|
+
const result = await pool.query(`update invoices
|
|
269
|
+
set status = 'paid', paid_at = coalesce(paid_at, now()),
|
|
270
|
+
tx_hash = coalesce($2, tx_hash), paid_by = coalesce(paid_by, $3), updated_at = now()
|
|
271
|
+
where coinpay_payment_id = $1 and status in ('sent', 'paid')`, [input.paymentId, input.txHash, input.paidBy ?? null]);
|
|
272
|
+
return (result.rowCount ?? 0) > 0;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A webhook from CoinPay, already verified by the caller.
|
|
276
|
+
*
|
|
277
|
+
* Only the two settlement events change anything. An expired quote is left
|
|
278
|
+
* to the next page load, which clears it the same way and does not depend on
|
|
279
|
+
* a delivery that may never come.
|
|
280
|
+
*/
|
|
281
|
+
export async function applyWebhook(pool, payload) {
|
|
282
|
+
const event = typeof payload['event'] === 'string' ? payload['event'] : '';
|
|
283
|
+
const paymentId = typeof payload['payment_id'] === 'string' ? payload['payment_id'] : '';
|
|
284
|
+
if (paymentId === '')
|
|
285
|
+
return 'ignored';
|
|
286
|
+
if (event !== 'payment.confirmed' && event !== 'payment.forwarded')
|
|
287
|
+
return 'ignored';
|
|
288
|
+
const txHash = typeof payload['tx_hash'] === 'string' ? payload['tx_hash'] : null;
|
|
289
|
+
const paid = await markPaid(pool, { paymentId, txHash });
|
|
290
|
+
return paid ? 'paid' : 'unknown';
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=invoices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoices.js","sourceRoot":"","sources":["../../src/core/invoices.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,UAAU,EAAsB,MAAM,cAAc,CAAC;AAiD9E,MAAM,MAAM,GAAG;;sDAEuC,CAAC;AAEvD,SAAS,SAAS,CAAC,GAAe,EAAE,OAA6B;IAC/D,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE;QACvE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5C,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EACL,GAAG,CAAC,kBAAkB,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI;YACjD,CAAC,CAAC,IAAI;YACN,CAAC,CAAC;gBACE,EAAE,EAAE,GAAG,CAAC,kBAAkB;gBAC1B,OAAO,EAAE,GAAG,CAAC,eAAe;gBAC5B,YAAY,EAAE,GAAG,CAAC,aAAa;gBAC/B,SAAS,EAAE,GAAG,CAAC,kBAAkB;gBACjC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC;aAC5C;QACP,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,SAAS,EAAE,GAAG,CAAC,UAAU;KAC1B,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,GAAG,SAAS;QAAE,OAAO,IAAI,CAAC;IACrD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAa,EACb,OAA6B,EAC7B,KAMC;IAED,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,4CAA4C,CAAC;IAC1E,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,iEAAiE,CAAC;IAC9F,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAExE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,OAAO,KAAK,IAAI;QAClB,OAAO,sEAAsE,CAAC;IAChF,IAAI,CAAC,OAAO,CAAC,MAAM;QACjB,OAAO,0EAA0E,CAAC;IACpF,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,iHAAiH,CAAC;IAC3H,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACvD,MAAM,MAAM,GACV,MAAM,KAAK,EAAE;QACX,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAC5B,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;YACpB,CAAC,CAAC,SAAS;QACb,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IACtD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,KAAK,EAAE;YAClB,CAAC,CAAC,iCAAiC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACpF,CAAC,CAAC,eAAe,MAAM,0CAA0C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACvH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,yEAAyE,EACzE,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAChC,CAAC;QACF,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,OAAO,mCAAmC,CAAC;QAC7C,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CACjC;oDAC8C,EAC9C,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CACnF,CAAC;QACF,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,gBAAgB,MAAM,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,KAAK,CAChC;+DACyD,EACzD,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CACjD,CAAC;QACF,MAAM,MAAM,CAAC,KAAK,CAAC,uDAAuD,EAAE;YAC1E,KAAK,CAAC,QAAQ;YACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,UAAU;SAC5B,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAChB,wFAAwF,EACxF,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,UAAU,CAAC,CAC7D,CAAC;QACF,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAa,GAAG,MAAM,kBAAkB,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QACnF,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/B,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAa,EACb,OAA6B,EAC7B,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM,mDAAmD,EAC5D,CAAC,QAAQ,CAAC,CACX,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAa,EACb,OAA6B,EAC7B,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM;;;2CAG8B,EACvC,CAAC,QAAQ,CAAC,CACX,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAa,EACb,OAA6B,EAC7B,SAAiB,EACjB,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM;;;2EAG8D,EACvE,CAAC,SAAS,EAAE,QAAQ,CAAC,CACtB,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAa,EACb,SAAiB,EACjB,OAAe;IAEf,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;0DACsD,EACtD,CAAC,SAAS,EAAE,OAAO,CAAC,CACrB,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAa,EACb,OAA6B,EAC7B,KAAgE;IAEhE,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,4CAA4C,CAAC;IAC1E,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAChF,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,kBAAkB,CAAC;IAChD,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO;QAAE,OAAO,kCAAkC,CAAC;IAClF,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,+BAA+B,CAAC;IACtE,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,6BAA6B,CAAC;IAEzE,4EAA4E;IAC5E,4DAA4D;IAC5D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IAC5C,IACE,MAAM,CAAC,OAAO,KAAK,IAAI;QACvB,MAAM,CAAC,OAAO,CAAC,SAAS,KAAK,IAAI;QACjC,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,EAClE,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC;YACnC,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,KAAK,EAAE,OAAO,CAAC,QAAQ;YACvB,YAAY,EAAE,OAAO,CAAC,aAAa;YACnC,WAAW,EACT,OAAO,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,CAAC,WAAW,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW;YACxF,WAAW,EAAE,GAAG,KAAK,CAAC,SAAS,UAAU,OAAO,CAAC,QAAQ,EAAE;YAC3D,mEAAmE;YACnE,cAAc,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE;YAClE,QAAQ,EAAE;gBACR,MAAM,EAAE,aAAa;gBACrB,UAAU,EAAE,OAAO,CAAC,EAAE;gBACtB,SAAS,EAAE,OAAO,CAAC,QAAQ;gBAC3B,QAAQ,EAAE,KAAK,CAAC,OAAO;aACxB;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,cAAc;YAAE,OAAO,KAAK,CAAC,OAAO,CAAC;QAC1D,MAAM,KAAK,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,4EAA4E;IAC5E,8CAA8C;IAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAC9B;;wCAEoC,EACpC;QACE,OAAO,CAAC,EAAE;QACV,MAAM,CAAC,EAAE;QACT,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;KACrE,CACF,CAAC;IACF,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,CAAC;QAC/B,OAAO,2DAA2D,CAAC;IACrE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAa,GAAG,MAAM,kBAAkB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;AACtE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAa,EACb,OAA6B,EAC7B,OAAgB;IAEhB,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IAC9F,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IAErC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;SAAM,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,CAAC,KAAK,CACd;;sEAEgE,EAChE,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CACzB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAa,GAAG,MAAM,kBAAkB,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACtF,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAa,EACb,KAA2E;IAE3E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;mEAG+D,EAC/D,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,CACtD,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAa,EACb,OAAgC;IAEhC,MAAM,KAAK,GAAG,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,MAAM,SAAS,GAAG,OAAO,OAAO,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,KAAK,KAAK,mBAAmB,IAAI,KAAK,KAAK,mBAAmB;QAAE,OAAO,SAAS,CAAC;IACrF,MAAM,MAAM,GAAG,OAAO,OAAO,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACnC,CAAC"}
|
package/dist/core/jobs.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type pg from 'pg';
|
|
9
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
|
+
import { type Pay } from '../schema/pay.ts';
|
|
10
11
|
interface JobRow {
|
|
11
12
|
id: string;
|
|
12
13
|
slug: string;
|
|
@@ -23,6 +24,8 @@ interface JobRow {
|
|
|
23
24
|
salary_period: string;
|
|
24
25
|
salary_equity: string | null;
|
|
25
26
|
salary_unpaid: boolean | null;
|
|
27
|
+
pay_lines: unknown;
|
|
28
|
+
pay_method: string | null;
|
|
26
29
|
tags: string[];
|
|
27
30
|
stack: string[];
|
|
28
31
|
requirements: string[];
|
|
@@ -46,6 +49,17 @@ interface JobRow {
|
|
|
46
49
|
org_description: string | null;
|
|
47
50
|
org_created_at: string;
|
|
48
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Why a listing cannot go live, or null when it can.
|
|
54
|
+
*
|
|
55
|
+
* Pay is required to publish. A listing that does not say what it pays gets
|
|
56
|
+
* fewer and worse applications, and on a board whose readers are agents
|
|
57
|
+
* deciding whether to bother, "not stated" is a listing nobody can act on.
|
|
58
|
+
* "Unpaid" counts: it is an answer. This is checked at publish rather than
|
|
59
|
+
* at creation so a draft can be written in pieces, by a person or a model,
|
|
60
|
+
* and only the moment it becomes public is held to it.
|
|
61
|
+
*/
|
|
62
|
+
export declare function publishProblem(job: Pick<Job, 'pay'>): string | null;
|
|
49
63
|
export declare function toJob(row: JobRow): Job;
|
|
50
64
|
export declare function searchJobs(pool: pg.Pool, query: JobQuery, options?: {
|
|
51
65
|
includeUnpublished?: boolean;
|
|
@@ -63,12 +77,7 @@ export interface JobInput {
|
|
|
63
77
|
seniority: Seniority | null;
|
|
64
78
|
location: string | null;
|
|
65
79
|
remoteRegions: string[];
|
|
66
|
-
|
|
67
|
-
salaryMax: number | null;
|
|
68
|
-
salaryCurrency: string;
|
|
69
|
-
salaryPeriod: string;
|
|
70
|
-
salaryEquity: string | null;
|
|
71
|
-
salaryUnpaid: boolean;
|
|
80
|
+
pay: Pay;
|
|
72
81
|
tags: string[];
|
|
73
82
|
stack: string[];
|
|
74
83
|
requirements: string[];
|
|
@@ -113,6 +122,15 @@ export declare function updateJobFromImport(pool: pg.Pool, id: string, fields: {
|
|
|
113
122
|
* the slug is the listing's public URL and editing must not break links to it.
|
|
114
123
|
*/
|
|
115
124
|
export declare function editJob(pool: pg.Pool, id: string, input: JobInput): Promise<Job | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Change only what a listing pays, leaving the rest of it alone.
|
|
127
|
+
*
|
|
128
|
+
* The one field that is required to publish deserves a way to be set
|
|
129
|
+
* without rewriting the listing around it: a draft written last week, or a
|
|
130
|
+
* listing that went live before pay was required, needs exactly this and
|
|
131
|
+
* nothing else.
|
|
132
|
+
*/
|
|
133
|
+
export declare function setPay(pool: pg.Pool, id: string, pay: Pay): Promise<Job | null>;
|
|
116
134
|
/** The listing imported from this URL, if there is one. */
|
|
117
135
|
export declare function getJobBySourceUrl(pool: pg.Pool, url: string): Promise<Job | null>;
|
|
118
136
|
export declare function countJobs(pool: pg.Pool): Promise<{
|
package/dist/core/jobs.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { DEFAULT_APPLY_SCHEMA, isAgentPolicy, isEmploymentType, isSeniority, isSalaryPeriod, isWorkplace, } from "../schema/index.js";
|
|
9
9
|
import { clean, parseList, slugify, suffix } from "../schema/text.js";
|
|
10
10
|
import { PER_YEAR } from "../schema/salary.js";
|
|
11
|
+
import { isPayType, normalisePay, payFromSalary, payStated, salaryFromPay, } from "../schema/pay.js";
|
|
11
12
|
// Filtering and ordering must compare periods on the same basis as the
|
|
12
13
|
// structured data and merged searches. Cast before multiplying: the stored
|
|
13
14
|
// integer range can annualise beyond Postgres's 32-bit integer limit.
|
|
@@ -20,7 +21,8 @@ const ANNUAL_TOP_SALARY = `case when j.salary_unpaid then null else
|
|
|
20
21
|
const SELECT = `
|
|
21
22
|
select j.id, j.slug, j.title, j.description, j.employment_type, j.workplace, j.seniority,
|
|
22
23
|
j.location, j.remote_regions, j.salary_min, j.salary_max, j.salary_currency,
|
|
23
|
-
j.salary_period, j.salary_equity, j.salary_unpaid, j.
|
|
24
|
+
j.salary_period, j.salary_equity, j.salary_unpaid, j.pay_lines, j.pay_method,
|
|
25
|
+
j.tags, j.stack, j.requirements, j.responsibilities,
|
|
24
26
|
j.agent_policy, j.apply_via, j.apply_url, j.apply_email, j.apply_schema,
|
|
25
27
|
j.apply_source_url, j.status,
|
|
26
28
|
j.published_at, j.expires_at, j.created_at, j.updated_at,
|
|
@@ -53,6 +55,61 @@ function toOrganisation(row) {
|
|
|
53
55
|
function toApplyMethod(row) {
|
|
54
56
|
return { via: 'board', schema: row.apply_schema ?? DEFAULT_APPLY_SCHEMA };
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The pay lines, read back out of JSON with the same suspicion as any other
|
|
60
|
+
* column that came from outside: a row written by an older build, a restore
|
|
61
|
+
* or a hand edit must render, not throw. A row with no lines but a salary
|
|
62
|
+
* range is one the backfill has not reached, and reads as that one line.
|
|
63
|
+
*/
|
|
64
|
+
function payOf(row) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
if (Array.isArray(row.pay_lines)) {
|
|
67
|
+
for (const entry of row.pay_lines) {
|
|
68
|
+
if (typeof entry !== 'object' || entry === null)
|
|
69
|
+
continue;
|
|
70
|
+
const line = entry;
|
|
71
|
+
if (!isPayType(line['type']))
|
|
72
|
+
continue;
|
|
73
|
+
const number = (value) => typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
74
|
+
lines.push({
|
|
75
|
+
type: line['type'],
|
|
76
|
+
min: number(line['min']),
|
|
77
|
+
max: number(line['max']),
|
|
78
|
+
currency: typeof line['currency'] === 'string' ? line['currency'] : 'USD',
|
|
79
|
+
unit: typeof line['unit'] === 'string' ? line['unit'] : null,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const unpaid = row.salary_unpaid === true;
|
|
84
|
+
if (lines.length === 0 && !unpaid && (row.salary_min !== null || row.salary_max !== null)) {
|
|
85
|
+
return {
|
|
86
|
+
...payFromSalary({
|
|
87
|
+
min: row.salary_min,
|
|
88
|
+
max: row.salary_max,
|
|
89
|
+
currency: row.salary_currency,
|
|
90
|
+
period: row.salary_period,
|
|
91
|
+
}),
|
|
92
|
+
method: row.pay_method,
|
|
93
|
+
equity: row.salary_equity,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { lines: unpaid ? [] : lines, method: row.pay_method, equity: row.salary_equity, unpaid };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Why a listing cannot go live, or null when it can.
|
|
100
|
+
*
|
|
101
|
+
* Pay is required to publish. A listing that does not say what it pays gets
|
|
102
|
+
* fewer and worse applications, and on a board whose readers are agents
|
|
103
|
+
* deciding whether to bother, "not stated" is a listing nobody can act on.
|
|
104
|
+
* "Unpaid" counts: it is an answer. This is checked at publish rather than
|
|
105
|
+
* at creation so a draft can be written in pieces, by a person or a model,
|
|
106
|
+
* and only the moment it becomes public is held to it.
|
|
107
|
+
*/
|
|
108
|
+
export function publishProblem(job) {
|
|
109
|
+
if (payStated(job.pay))
|
|
110
|
+
return null;
|
|
111
|
+
return 'Say what it pays before publishing: an amount per hour, day, week, month or year, a fixed fee, a price per task, a revenue share, or that it is unpaid.';
|
|
112
|
+
}
|
|
56
113
|
export function toJob(row) {
|
|
57
114
|
return {
|
|
58
115
|
id: row.id,
|
|
@@ -67,6 +124,7 @@ export function toJob(row) {
|
|
|
67
124
|
seniority: isSeniority(row.seniority) ? row.seniority : null,
|
|
68
125
|
location: row.location,
|
|
69
126
|
remoteRegions: row.remote_regions ?? [],
|
|
127
|
+
pay: payOf(row),
|
|
70
128
|
salary: {
|
|
71
129
|
min: row.salary_min,
|
|
72
130
|
max: row.salary_max,
|
|
@@ -195,15 +253,11 @@ export function normaliseInput(input, orgId) {
|
|
|
195
253
|
const workplace = isWorkplace(input['workplace']) ? input['workplace'] : 'remote';
|
|
196
254
|
const seniority = isSeniority(input['seniority']) ? input['seniority'] : null;
|
|
197
255
|
const agentPolicy = isAgentPolicy(input['agentPolicy']) ? input['agentPolicy'] : 'disclose';
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const salaryMax = salaryUnpaid ? null : money(input['salaryMax']);
|
|
204
|
-
if (salaryMin !== null && salaryMax !== null && salaryMax < salaryMin) {
|
|
205
|
-
return 'The top of the salary range is below the bottom of it.';
|
|
206
|
-
}
|
|
256
|
+
// Pay lines, the settlement method, equity and "unpaid", from the new
|
|
257
|
+
// `pay` field or from the flat salary fields older clients still send.
|
|
258
|
+
const pay = normalisePay(input);
|
|
259
|
+
if (typeof pay === 'string')
|
|
260
|
+
return pay;
|
|
207
261
|
const apply = normaliseApply(input);
|
|
208
262
|
if (typeof apply === 'string')
|
|
209
263
|
return apply;
|
|
@@ -217,12 +271,7 @@ export function normaliseInput(input, orgId) {
|
|
|
217
271
|
seniority,
|
|
218
272
|
location: location === '' ? null : location,
|
|
219
273
|
remoteRegions: parseList(input['remoteRegions'], 30, 8).map((code) => code.toUpperCase()),
|
|
220
|
-
|
|
221
|
-
salaryMax,
|
|
222
|
-
salaryCurrency: (clean(input['salaryCurrency'], 3) || 'USD').toUpperCase(),
|
|
223
|
-
salaryPeriod: isSalaryPeriod(input['salaryPeriod']) ? input['salaryPeriod'] : 'year',
|
|
224
|
-
salaryEquity: clean(input['salaryEquity'], 60) || null,
|
|
225
|
-
salaryUnpaid,
|
|
274
|
+
pay,
|
|
226
275
|
tags: parseList(input['tags'], 12),
|
|
227
276
|
stack: parseList(input['stack'], 20),
|
|
228
277
|
requirements: lines(input['requirements'], 20),
|
|
@@ -344,13 +393,16 @@ async function uniqueSlug(pool, title) {
|
|
|
344
393
|
}
|
|
345
394
|
export async function createJob(pool, input) {
|
|
346
395
|
const slug = await uniqueSlug(pool, input.title);
|
|
396
|
+
const salary = salaryFromPay(input.pay);
|
|
347
397
|
const result = await pool.query(`insert into jobs (
|
|
348
398
|
slug, org_id, title, description, employment_type, workplace, seniority, location,
|
|
349
399
|
remote_regions, salary_min, salary_max, salary_currency, salary_period, salary_equity,
|
|
350
400
|
salary_unpaid, tags, stack, requirements, responsibilities, agent_policy, apply_via,
|
|
351
|
-
apply_url, apply_email, apply_schema, apply_source_url, expires_at, status
|
|
401
|
+
apply_url, apply_email, apply_schema, apply_source_url, expires_at, status,
|
|
402
|
+
pay_lines, pay_method
|
|
352
403
|
) values (
|
|
353
|
-
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,'draft'
|
|
404
|
+
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,'draft',
|
|
405
|
+
$27,$28
|
|
354
406
|
) returning id`, [
|
|
355
407
|
slug,
|
|
356
408
|
input.orgId,
|
|
@@ -361,12 +413,12 @@ export async function createJob(pool, input) {
|
|
|
361
413
|
input.seniority,
|
|
362
414
|
input.location,
|
|
363
415
|
input.remoteRegions,
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
input.
|
|
369
|
-
input.
|
|
416
|
+
salary.min,
|
|
417
|
+
salary.max,
|
|
418
|
+
salary.currency,
|
|
419
|
+
salary.period,
|
|
420
|
+
input.pay.equity,
|
|
421
|
+
input.pay.unpaid,
|
|
370
422
|
input.tags,
|
|
371
423
|
input.stack,
|
|
372
424
|
input.requirements,
|
|
@@ -378,6 +430,8 @@ export async function createJob(pool, input) {
|
|
|
378
430
|
JSON.stringify(input.apply.schema),
|
|
379
431
|
input.sourceUrl,
|
|
380
432
|
input.expiresAt,
|
|
433
|
+
JSON.stringify(input.pay.lines),
|
|
434
|
+
input.pay.method,
|
|
381
435
|
]);
|
|
382
436
|
const id = result.rows[0]?.id;
|
|
383
437
|
if (id === undefined)
|
|
@@ -433,13 +487,15 @@ export async function updateJobFromImport(pool, id, fields) {
|
|
|
433
487
|
* the slug is the listing's public URL and editing must not break links to it.
|
|
434
488
|
*/
|
|
435
489
|
export async function editJob(pool, id, input) {
|
|
490
|
+
const salary = salaryFromPay(input.pay);
|
|
436
491
|
await pool.query(`update jobs
|
|
437
492
|
set title = $2, description = $3, employment_type = $4, workplace = $5,
|
|
438
493
|
seniority = $6, location = $7, remote_regions = $8,
|
|
439
494
|
salary_min = $9, salary_max = $10, salary_currency = $11,
|
|
440
495
|
salary_period = $12, salary_equity = $13, salary_unpaid = $14,
|
|
441
496
|
tags = $15, stack = $16, requirements = $17, responsibilities = $18,
|
|
442
|
-
agent_policy = $19, expires_at = $20,
|
|
497
|
+
agent_policy = $19, expires_at = $20, pay_lines = $21, pay_method = $22,
|
|
498
|
+
updated_at = now()
|
|
443
499
|
where id = $1`, [
|
|
444
500
|
id,
|
|
445
501
|
input.title,
|
|
@@ -449,18 +505,47 @@ export async function editJob(pool, id, input) {
|
|
|
449
505
|
input.seniority,
|
|
450
506
|
input.location,
|
|
451
507
|
input.remoteRegions,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
input.
|
|
457
|
-
input.
|
|
508
|
+
salary.min,
|
|
509
|
+
salary.max,
|
|
510
|
+
salary.currency,
|
|
511
|
+
salary.period,
|
|
512
|
+
input.pay.equity,
|
|
513
|
+
input.pay.unpaid,
|
|
458
514
|
input.tags,
|
|
459
515
|
input.stack,
|
|
460
516
|
input.requirements,
|
|
461
517
|
input.responsibilities,
|
|
462
518
|
input.agentPolicy,
|
|
463
519
|
input.expiresAt,
|
|
520
|
+
JSON.stringify(input.pay.lines),
|
|
521
|
+
input.pay.method,
|
|
522
|
+
]);
|
|
523
|
+
return getJobById(pool, id);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Change only what a listing pays, leaving the rest of it alone.
|
|
527
|
+
*
|
|
528
|
+
* The one field that is required to publish deserves a way to be set
|
|
529
|
+
* without rewriting the listing around it: a draft written last week, or a
|
|
530
|
+
* listing that went live before pay was required, needs exactly this and
|
|
531
|
+
* nothing else.
|
|
532
|
+
*/
|
|
533
|
+
export async function setPay(pool, id, pay) {
|
|
534
|
+
const salary = salaryFromPay(pay);
|
|
535
|
+
await pool.query(`update jobs
|
|
536
|
+
set salary_min = $2, salary_max = $3, salary_currency = $4, salary_period = $5,
|
|
537
|
+
salary_equity = $6, salary_unpaid = $7, pay_lines = $8, pay_method = $9,
|
|
538
|
+
updated_at = now()
|
|
539
|
+
where id = $1`, [
|
|
540
|
+
id,
|
|
541
|
+
salary.min,
|
|
542
|
+
salary.max,
|
|
543
|
+
salary.currency,
|
|
544
|
+
salary.period,
|
|
545
|
+
pay.equity,
|
|
546
|
+
pay.unpaid,
|
|
547
|
+
JSON.stringify(pay.lines),
|
|
548
|
+
pay.method,
|
|
464
549
|
]);
|
|
465
550
|
return getJobById(pool, id);
|
|
466
551
|
}
|