@profullstack/agenticjobs 0.5.0 → 0.6.1

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.
Files changed (51) hide show
  1. package/README.md +42 -4
  2. package/dist/cli/index.js +70 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/client/client.d.ts +36 -0
  5. package/dist/client/client.js +22 -0
  6. package/dist/client/client.js.map +1 -1
  7. package/dist/config.d.ts +1 -1
  8. package/dist/config.js +1 -1
  9. package/dist/core/auth.js +4 -1
  10. package/dist/core/auth.js.map +1 -1
  11. package/dist/core/markdown-media.d.ts +51 -0
  12. package/dist/core/markdown-media.js +224 -0
  13. package/dist/core/markdown-media.js.map +1 -0
  14. package/dist/core/resumes.d.ts +12 -0
  15. package/dist/core/resumes.js +19 -0
  16. package/dist/core/resumes.js.map +1 -1
  17. package/dist/core/updates.d.ts +149 -0
  18. package/dist/core/updates.js +332 -0
  19. package/dist/core/updates.js.map +1 -0
  20. package/dist/directory/fetch.d.ts +1 -1
  21. package/dist/mcp/tools.js +78 -0
  22. package/dist/mcp/tools.js.map +1 -1
  23. package/dist/server/middleware.d.ts +11 -6
  24. package/dist/server/middleware.js +12 -7
  25. package/dist/server/middleware.js.map +1 -1
  26. package/dist/server/routes/api.js +167 -0
  27. package/dist/server/routes/api.js.map +1 -1
  28. package/dist/server/routes/discovery.js +130 -11
  29. package/dist/server/routes/discovery.js.map +1 -1
  30. package/dist/server/routes/openapi.js +105 -0
  31. package/dist/server/routes/openapi.js.map +1 -1
  32. package/dist/server/routes/pages.js +240 -10
  33. package/dist/server/routes/pages.js.map +1 -1
  34. package/dist/views/candidates.d.ts +3 -0
  35. package/dist/views/candidates.js +3 -2
  36. package/dist/views/candidates.js.map +1 -1
  37. package/dist/views/jobs.d.ts +40 -1
  38. package/dist/views/jobs.js +69 -4
  39. package/dist/views/jobs.js.map +1 -1
  40. package/dist/views/layout.js +2 -2
  41. package/dist/views/layout.js.map +1 -1
  42. package/dist/views/me.d.ts +11 -0
  43. package/dist/views/me.js +5 -2
  44. package/dist/views/me.js.map +1 -1
  45. package/dist/views/updates.d.ts +80 -0
  46. package/dist/views/updates.js +49 -0
  47. package/dist/views/updates.js.map +1 -0
  48. package/migrations/0010_updates_and_follows.sql +70 -0
  49. package/package.json +17 -15
  50. package/web/public/app.css +44 -0
  51. package/web/public/sw.js +1 -1
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Markdown to media: the same resume as HTML, PDF and DOCX.
3
+ *
4
+ * The counterpart to media2markdown, and deliberately built in rather than
5
+ * stood up as a second site: this board already holds the document, already
6
+ * parses it, and a converter behind its own domain is a second thing to keep
7
+ * running for one button.
8
+ *
9
+ * The Markdown stays canonical. Everything here is a rendering of it, made on
10
+ * request and never stored, so there is no second copy to drift.
11
+ *
12
+ * The layout is adapted from the resume generator at ralyodio/resume, which
13
+ * used Puppeteer. Chromium is ~350MB in an image whose builds already take ten
14
+ * minutes, and a browser per download is a slow way to set type, so the CSS is
15
+ * written for a print engine instead: no grid, and flex only where a row of
16
+ * items genuinely is a row. That renders under weasyprint, which is a tenth of
17
+ * the size, and under a browser if one is ever there.
18
+ */
19
+ import { spawn } from 'node:child_process';
20
+ import { escapeHtml } from "../markup/escape.js";
21
+ import { renderMarkdown } from "../markup/markdown.js";
22
+ export class ConversionProblem extends Error {
23
+ }
24
+ /** Long enough for a resume, short enough that a hung converter is not a leak. */
25
+ const TIMEOUT_MS = 20_000;
26
+ /** A resume that renders to more than this is not a resume. */
27
+ const MAX_BYTES = 12 * 1024 * 1024;
28
+ /**
29
+ * Print stylesheet, adapted from the resume generator.
30
+ *
31
+ * Kept as one string rather than a file because it has to be inlined: a PDF
32
+ * engine reading `file://` HTML fetches nothing, and a stylesheet link that
33
+ * silently fails is an unstyled resume nobody notices until it is sent.
34
+ */
35
+ const STYLE = `
36
+ @page { size: Letter; margin: 0.5in 0.55in; }
37
+ * { box-sizing: border-box; }
38
+ body {
39
+ margin: 0 auto; max-width: 8.5in; color: #20242a; background: #fff;
40
+ font-family: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
41
+ font-size: 10pt; line-height: 1.35;
42
+ }
43
+ a { color: #1f4f82; text-decoration: none; }
44
+ .header { padding-bottom: 0.16in; margin-bottom: 0.2in; border-bottom: 2px solid #1f2937; }
45
+ .header h1 {
46
+ margin: 0 0 0.04in; color: #111827; font-size: 26pt; font-weight: 800;
47
+ line-height: 1; letter-spacing: -0.8px;
48
+ }
49
+ .header .subtitle {
50
+ margin: 0 0 0.11in; color: #374151; font-size: 10.5pt; font-weight: 600; line-height: 1.25;
51
+ }
52
+ .contact { display: flex; flex-wrap: wrap; color: #4b5563; font-size: 8.8pt; }
53
+ .contact span { white-space: nowrap; margin: 0 0.16in 0.045in 0; }
54
+ h2 {
55
+ margin: 0.2in 0 0.07in; padding-bottom: 0.025in; border-bottom: 1px solid #d1d5db;
56
+ color: #111827; font-size: 9.2pt; font-weight: 800; line-height: 1.15;
57
+ text-transform: uppercase; letter-spacing: 1.1px;
58
+ }
59
+ h3 {
60
+ margin: 0.115in 0 0.02in; color: #111827; font-size: 10.1pt; font-weight: 700;
61
+ line-height: 1.18; break-after: avoid; page-break-after: avoid;
62
+ }
63
+ h3 + p { color: #4b5563; font-size: 8.9pt; margin: 0 0 0.04in; }
64
+ p { margin: 0 0 0.07in; }
65
+ ul { margin: 0 0 0.08in; padding-left: 0.17in; }
66
+ li { margin: 0 0 0.02in; }
67
+ /* An entry should not be split across a page if it can be helped. */
68
+ h3, li { break-inside: avoid; page-break-inside: avoid; }
69
+ code { font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.92em; }
70
+ hr { border: 0; border-top: 1px solid #e5e7eb; margin: 0.14in 0; }
71
+ @media print { body { max-width: none; } }
72
+ `;
73
+ /**
74
+ * The resume as a standalone HTML document.
75
+ *
76
+ * Built from the parsed document where it has structure, so the name and the
77
+ * contact line get the treatment they deserve, and from the rendered Markdown
78
+ * for the body, which is the same renderer the site uses. A resume that parsed
79
+ * as nothing still renders: it just gets the body and no header.
80
+ */
81
+ export function resumeHtml(options) {
82
+ const name = options.parsed?.name ?? options.title;
83
+ const headline = options.parsed?.headline ?? null;
84
+ const contact = options.parsed?.contact ?? [];
85
+ // The h1 and the contact bullets are rendered as the header, so the body
86
+ // starts after them rather than repeating them as ordinary Markdown.
87
+ const body = options.parsed === null ? options.markdown : withoutHeader(options.markdown);
88
+ return [
89
+ '<!doctype html>',
90
+ '<html lang="en"><head><meta charset="utf-8">',
91
+ `<title>${escapeHtml(name)}</title>`,
92
+ `<style>${STYLE}</style>`,
93
+ '</head><body>',
94
+ '<header class="header">',
95
+ `<h1>${escapeHtml(name)}</h1>`,
96
+ headline === null ? '' : `<p class="subtitle">${escapeHtml(headline)}</p>`,
97
+ contact.length === 0
98
+ ? ''
99
+ : `<div class="contact">${contact
100
+ .map((item) => item.href === null
101
+ ? `<span>${escapeHtml(item.value)}</span>`
102
+ : `<span><a href="${escapeHtml(item.href)}">${escapeHtml(item.value)}</a></span>`)
103
+ .join('')}</div>`,
104
+ '</header>',
105
+ renderMarkdown(body, { noImages: true }),
106
+ '</body></html>',
107
+ ].join('\n');
108
+ }
109
+ /** Drop the leading h1 and the contact bullets that follow it. */
110
+ function withoutHeader(markdown) {
111
+ const lines = markdown.replace(/\r\n?/g, '\n').split('\n');
112
+ let index = 0;
113
+ while (index < lines.length && (lines[index] ?? '').trim() === '')
114
+ index += 1;
115
+ if (!(lines[index] ?? '').startsWith('# '))
116
+ return markdown;
117
+ index += 1;
118
+ // Everything up to the first heading or paragraph is the contact block.
119
+ while (index < lines.length) {
120
+ const line = (lines[index] ?? '').trim();
121
+ if (line === '' || line.startsWith('- ') || line.startsWith('* ')) {
122
+ index += 1;
123
+ continue;
124
+ }
125
+ break;
126
+ }
127
+ return lines.slice(index).join('\n');
128
+ }
129
+ /**
130
+ * Run a converter and hand back what it wrote.
131
+ *
132
+ * The document goes in on stdin and comes back on stdout, so nothing touches
133
+ * the filesystem: a converter that writes temp files is a converter that
134
+ * leaves them behind when it is killed.
135
+ */
136
+ async function convert(command, args, input) {
137
+ return new Promise((resolve, reject) => {
138
+ const child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });
139
+ const out = [];
140
+ let size = 0;
141
+ let stderr = '';
142
+ const timer = setTimeout(() => {
143
+ child.kill('SIGKILL');
144
+ reject(new ConversionProblem(`${command} took longer than ${TIMEOUT_MS / 1000}s`));
145
+ }, TIMEOUT_MS);
146
+ child.stdout.on('data', (chunk) => {
147
+ size += chunk.length;
148
+ if (size > MAX_BYTES) {
149
+ child.kill('SIGKILL');
150
+ reject(new ConversionProblem(`${command} produced more than ${MAX_BYTES} bytes`));
151
+ return;
152
+ }
153
+ out.push(chunk);
154
+ });
155
+ child.stderr.on('data', (chunk) => {
156
+ stderr = `${stderr}${chunk.toString()}`.slice(0, 2000);
157
+ });
158
+ child.on('error', (error) => {
159
+ clearTimeout(timer);
160
+ reject(new ConversionProblem(`${command} could not be run: ${error.message}`));
161
+ });
162
+ child.on('close', (code) => {
163
+ clearTimeout(timer);
164
+ if (code !== 0) {
165
+ reject(new ConversionProblem(`${command} exited ${code}: ${stderr.trim()}`));
166
+ return;
167
+ }
168
+ resolve(Buffer.concat(out));
169
+ });
170
+ child.stdin.on('error', () => undefined);
171
+ child.stdin.end(input);
172
+ });
173
+ }
174
+ /** Word, straight from the Markdown, by the converter already in the image. */
175
+ export async function resumeDocx(markdown) {
176
+ return convert('pandoc', ['-f', 'markdown', '-t', 'docx', '-o', '-'], markdown);
177
+ }
178
+ /**
179
+ * PDF, from the styled HTML rather than from the Markdown.
180
+ *
181
+ * Going through HTML is what keeps the PDF and the web page the same document:
182
+ * pandoc's own PDF output would be a different design maintained separately.
183
+ *
184
+ * Engines are tried in order and the first one present wins, so an image with
185
+ * a browser in it uses that and an image with neither says which are missing
186
+ * instead of failing with "exit 127".
187
+ */
188
+ export async function resumePdf(html) {
189
+ const engines = [
190
+ ['weasyprint', ['-', '-']],
191
+ ['wkhtmltopdf', ['-q', '-', '-']],
192
+ ['chromium', ['--headless', '--no-sandbox', '--print-to-pdf=/dev/stdout', '-']],
193
+ ];
194
+ const problems = [];
195
+ for (const [command, args] of engines) {
196
+ try {
197
+ return await convert(command, args, html);
198
+ }
199
+ catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ // Only a missing binary is worth trying the next engine for. An engine
202
+ // that ran and failed has something to say, so it is not swallowed.
203
+ if (!message.includes('could not be run'))
204
+ throw error;
205
+ problems.push(command);
206
+ }
207
+ }
208
+ throw new ConversionProblem(`No PDF engine on this instance. Install one of: ${problems.join(', ')}.`);
209
+ }
210
+ /** What to call the file the reader saves. */
211
+ export function filename(name, format) {
212
+ const base = name
213
+ .toLowerCase()
214
+ .replace(/[^a-z0-9]+/g, '-')
215
+ .replace(/^-+|-+$/g, '')
216
+ .slice(0, 60) || 'resume';
217
+ return `${base}.${format}`;
218
+ }
219
+ export const CONTENT_TYPE = {
220
+ html: 'text/html; charset=utf-8',
221
+ pdf: 'application/pdf',
222
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
223
+ };
224
+ //# sourceMappingURL=markdown-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-media.js","sourceRoot":"","sources":["../../src/core/markdown-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,kFAAkF;AAClF,MAAM,UAAU,GAAG,MAAM,CAAC;AAC1B,+DAA+D;AAC/D,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCb,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,OAI1B;IACC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI,CAAC;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,CAAC;IAE9C,yEAAyE;IACzE,qEAAqE;IACrE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE1F,OAAO;QACL,iBAAiB;QACjB,8CAA8C;QAC9C,UAAU,UAAU,CAAC,IAAI,CAAC,UAAU;QACpC,UAAU,KAAK,UAAU;QACzB,eAAe;QACf,yBAAyB;QACzB,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO;QAC9B,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,uBAAuB,UAAU,CAAC,QAAQ,CAAC,MAAM;QAC1E,OAAO,CAAC,MAAM,KAAK,CAAC;YAClB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,wBAAwB,OAAO;iBAC5B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,IAAI,CAAC,IAAI,KAAK,IAAI;gBAChB,CAAC,CAAC,SAAS,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS;gBAC1C,CAAC,CAAC,kBAAkB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CACpF;iBACA,IAAI,CAAC,EAAE,CAAC,QAAQ;QACvB,WAAW;QACX,cAAc,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACxC,gBAAgB;KACjB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,kEAAkE;AAClE,SAAS,aAAa,CAAC,QAAgB;IACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3D,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,KAAK,IAAI,CAAC,CAAC;IAC9E,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5D,KAAK,IAAI,CAAC,CAAC;IACX,wEAAwE;IACxE,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACzC,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAClE,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM;IACR,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,OAAO,CAAC,OAAe,EAAE,IAAc,EAAE,KAAa;IACnE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACxE,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,MAAM,CAAC,IAAI,iBAAiB,CAAC,GAAG,OAAO,qBAAqB,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;QACrF,CAAC,EAAE,UAAU,CAAC,CAAC;QAEf,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,SAAS,EAAE,CAAC;gBACrB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,MAAM,CAAC,IAAI,iBAAiB,CAAC,GAAG,OAAO,uBAAuB,SAAS,QAAQ,CAAC,CAAC,CAAC;gBAClF,OAAO;YACT,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,MAAM,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,IAAI,iBAAiB,CAAC,GAAG,OAAO,sBAAsB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACjF,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,iBAAiB,CAAC,GAAG,OAAO,WAAW,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC7E,OAAO;YACT,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY;IAC1C,MAAM,OAAO,GAAyB;QACpC,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC,UAAU,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,4BAA4B,EAAE,GAAG,CAAC,CAAC;KAChF,CAAC;IAEF,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,uEAAuE;YACvE,oEAAoE;YACpE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;gBAAE,MAAM,KAAK,CAAC;YACvD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,MAAM,IAAI,iBAAiB,CACzB,mDAAmD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC1E,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,MAAmB;IACxD,MAAM,IAAI,GACR,IAAI;SACD,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,QAAQ,CAAC;IAC9B,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAgC;IACvD,IAAI,EAAE,0BAA0B;IAChC,GAAG,EAAE,iBAAiB;IACtB,IAAI,EAAE,yEAAyE;CAChF,CAAC"}
@@ -48,6 +48,18 @@ export declare function listPublicResumes(pool: pg.Pool, limit?: number): Promis
48
48
  * Serves `link` as well as `public`, which is the difference between the two:
49
49
  * a link resume is reachable by anyone holding the URL and is not listed.
50
50
  */
51
+ /**
52
+ * The file a shared resume was uploaded from, if it was uploaded at all.
53
+ *
54
+ * Keyed on the public slug rather than on an owner, because this is for the
55
+ * candidate page: handing back the PDF somebody actually uploaded beats
56
+ * handing back one regenerated from a parse of it.
57
+ */
58
+ export declare function publicResumeSource(pool: pg.Pool, publicSlug: string): Promise<{
59
+ name: string;
60
+ mime: string;
61
+ bytes: Buffer;
62
+ } | null>;
51
63
  export declare function getPublicResume(pool: pg.Pool, publicSlug: string): Promise<Resume | null>;
52
64
  export declare function getSharedResume(pool: pg.Pool, id: string): Promise<Resume | null>;
53
65
  export interface SaveResume {
@@ -119,6 +119,25 @@ export async function listPublicResumes(pool, limit = 100) {
119
119
  * Serves `link` as well as `public`, which is the difference between the two:
120
120
  * a link resume is reachable by anyone holding the URL and is not listed.
121
121
  */
122
+ /**
123
+ * The file a shared resume was uploaded from, if it was uploaded at all.
124
+ *
125
+ * Keyed on the public slug rather than on an owner, because this is for the
126
+ * candidate page: handing back the PDF somebody actually uploaded beats
127
+ * handing back one regenerated from a parse of it.
128
+ */
129
+ export async function publicResumeSource(pool, publicSlug) {
130
+ const result = await pool.query(`select source_name, source_mime, source_bytes from resumes
131
+ where public_slug = $1 and visibility in ('link', 'public')`, [publicSlug]);
132
+ const row = result.rows[0];
133
+ if (row === undefined || row.source_bytes === null)
134
+ return null;
135
+ return {
136
+ name: row.source_name ?? 'resume',
137
+ mime: row.source_mime ?? 'application/octet-stream',
138
+ bytes: row.source_bytes,
139
+ };
140
+ }
122
141
  export async function getPublicResume(pool, publicSlug) {
123
142
  const result = await pool.query(`${SELECT} where public_slug = $1 and visibility in ('link', 'public')`, [publicSlug]);
124
143
  const row = result.rows[0];
@@ -1 +1 @@
1
- {"version":3,"file":"resumes.js","sourceRoot":"","sources":["../../src/core/resumes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resumes.js","sourceRoot":"","sources":["../../src/core/resumes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAmB,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAGnE,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,YAAkC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC1F,CAAC;AA+BD,SAAS,QAAQ,CAAC,GAAc;IAC9B,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,UAAU,EAAE,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;QACrE,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;KAC1B,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,sBAAsB;AACtB,MAAM,MAAM,GAAG;wEACyD,CAAC;AAEzE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAa,EAAE,MAAc;IAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM,8CAA8C,EACvD,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAa,EACb,MAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAY,GAAG,MAAM,mCAAmC,EAAE;QACvF,MAAM;QACN,IAAI;KACL,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAa,EAAE,EAAU;IAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAY,GAAG,MAAM,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAClD,CAAC;AAED,+DAA+D;AAC/D,0EAA0E;AAC1E,MAAM,QAAQ,GAAG,EAAE,CAAC;AAEpB,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7D,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,WAAW,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,cAAc,CAAC,IAAa,EAAE,MAAc;IACzD,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxD,OAAO,UAAU,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAa,EAAE,MAAc;IAClE,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC;IAC9D,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC;IAEzD,4EAA4E;IAC5E,0EAA0E;IAC1E,2EAA2E;IAC3E,kEAAkE;IAClE,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,EAAE,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAC9B;;6BAEuB,EACvB,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CACvB,CAAC;QACF,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;IAChD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAa,EAAE,KAAK,GAAG,GAAG;IAChE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM;wCAC2B,EACpC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CACpC,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAa,EACb,UAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAK7B;kEAC8D,EAC9D,CAAC,UAAU,CAAC,CACb,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,YAAY,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChE,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,WAAW,IAAI,QAAQ;QACjC,IAAI,EAAE,GAAG,CAAC,WAAW,IAAI,0BAA0B;QACnD,KAAK,EAAE,GAAG,CAAC,YAAY;KACxB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAa,EAAE,UAAkB;IACrE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM,8DAA8D,EACvE,CAAC,UAAU,CAAC,CACb,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAa,EAAE,EAAU;IAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,MAAM,qDAAqD,EAC9D,CAAC,EAAE,CAAC,CACL,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAClD,CAAC;AASD,KAAK,UAAU,UAAU,CAAC,IAAa,EAAE,MAAc,EAAE,KAAa;IACpE,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,wDAAwD,EAAE;YACvF,MAAM;YACN,SAAS;SACV,CAAC,CAAC;QACH,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;IAChD,CAAC;IACD,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAa,EACb,MAAc,EACd,KAAiB;IAEjB,yDAAyD;IACzD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,4EAA4E;IAC5E,qCAAqC;IACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;IACjE,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAEnD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;sCAIkC,EAClC;QACE,MAAM;QACN,IAAI;QACJ,KAAK;QACL,QAAQ;QACR,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;QACtB,KAAK,CAAC,UAAU,IAAI,SAAS;QAC7B,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI;QAC1B,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI;QAC1B,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI;KAC5B,CACF,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACxE,OAAO,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAa,EACb,MAAc,EACd,IAAY,EACZ,KAAiB;IAEjB,yDAAyD;IACzD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B;;;;;;;oDAOgD,EAChD,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC,CACpG,CAAC;IACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAa,EAAE,MAAc,EAAE,IAAY;IAC5E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,sDAAsD,EAAE;QACtF,MAAM;QACN,IAAI;KACL,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAa,EACb,MAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAI5B,6FAA6F,EAAE;QAChG,MAAM;QACN,IAAI;KACL,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,YAAY,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChE,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,WAAW,IAAI,QAAQ;QACjC,IAAI,EAAE,GAAG,CAAC,WAAW,IAAI,0BAA0B;QACnD,KAAK,EAAE,GAAG,CAAC,YAAY;KACxB,CAAC;AACJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAa,EACb,MAAc,EACd,IAAmB;IAEnB,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE;QAChC,KAAK,EAAE,QAAQ;QACf,QAAQ,EAAE,cAAc,CAAC,IAAI,IAAI,WAAW,CAAC;KAC9C,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Updates, and following the people who post them.
3
+ *
4
+ * A short post from an employer or a candidate: we shipped this, we are hiring
5
+ * two more, I finished a contract and I am free in March. It is the thing a
6
+ * job board is missing between "posted a job" and silence, and the reason to
7
+ * follow anybody here.
8
+ *
9
+ * The whole design question is how not to become a spam feed, and the answer
10
+ * is that posting is expensive on purpose:
11
+ *
12
+ * - You post as an employer you belong to, or as yourself. There is no third
13
+ * option, so every update has a page behind it that can be read and judged.
14
+ * - Five a day, and no two the same. The rate limit is per author, in the
15
+ * database, so it holds however the update arrived.
16
+ * - 600 characters, one link. Long enough for news, short enough that nobody
17
+ * tries to publish an article here.
18
+ * - The body is text. Not Markdown, not HTML: the link is a column, so the
19
+ * one thing worth linking is already structured and nothing has to be
20
+ * sanitised out of prose.
21
+ */
22
+ import type pg from 'pg';
23
+ /** The most an update can say. */
24
+ export declare const BODY_MAX = 600;
25
+ /**
26
+ * The least. A three-word post with a link is an ad, and the floor is the
27
+ * cheapest thing that discourages one.
28
+ */
29
+ export declare const BODY_MIN = 12;
30
+ /** Per author, per day. */
31
+ export declare const DAILY_LIMIT = 5;
32
+ export type UpdateAuthorKind = 'employer' | 'candidate';
33
+ export interface UpdateAuthor {
34
+ kind: UpdateAuthorKind;
35
+ /** The slug of the employer, or of the author's public resume. */
36
+ slug: string | null;
37
+ name: string;
38
+ }
39
+ export interface Update {
40
+ id: string;
41
+ body: string;
42
+ link: string | null;
43
+ createdAt: string;
44
+ author: UpdateAuthor;
45
+ }
46
+ /** Who an update is posted as, and who is followed. */
47
+ export type Target = {
48
+ kind: 'employer';
49
+ orgId: string;
50
+ } | {
51
+ kind: 'candidate';
52
+ userId: string;
53
+ };
54
+ /**
55
+ * A link an update is allowed to carry.
56
+ *
57
+ * The host rules are the directory's, because the question is the same one:
58
+ * can somebody else's browser actually go there. `publishable` keeps only an
59
+ * origin, so it is used as the test and the full URL is what is stored; a
60
+ * link to a blog post is the normal case and stripping its path would make
61
+ * the field useless.
62
+ */
63
+ export declare function normaliseLink(value: unknown): string | null;
64
+ export interface UpdateInput {
65
+ body: unknown;
66
+ link?: unknown;
67
+ }
68
+ /**
69
+ * Post an update.
70
+ *
71
+ * Returns the update, or a sentence explaining why not. Every caller here is
72
+ * either a person looking at a form or a model reading an error body, and
73
+ * both can act on a sentence.
74
+ */
75
+ export declare function postUpdate(pool: pg.Pool, authorId: string, target: Target, input: UpdateInput): Promise<Update | string>;
76
+ /** Everything anybody posted, newest first. The board's news page. */
77
+ export declare function listUpdates(pool: pg.Pool, limit?: number): Promise<Update[]>;
78
+ export declare function listUpdatesFor(pool: pg.Pool, target: Target, limit?: number): Promise<Update[]>;
79
+ /**
80
+ * The updates from everyone one person follows.
81
+ *
82
+ * This is the only reason follows exist, so it is one query rather than a
83
+ * list of ids fetched and then looped over.
84
+ */
85
+ export declare function listFollowedUpdates(pool: pg.Pool, followerId: string, limit?: number): Promise<Update[]>;
86
+ /**
87
+ * Delete an update.
88
+ *
89
+ * Whoever wrote it, or anybody who posts for that employer: a person who
90
+ * leaves a company should not leave a post nobody there can take down.
91
+ */
92
+ export declare function deleteUpdate(pool: pg.Pool, viewerId: string, id: string): Promise<boolean>;
93
+ /**
94
+ * Follow. Following twice is following once.
95
+ *
96
+ * The unique index does the work, so a double-submitted form and a retried
97
+ * API call are both no-ops rather than an error the caller has to read.
98
+ */
99
+ export declare function follow(pool: pg.Pool, followerId: string, target: Target): Promise<boolean>;
100
+ export declare function unfollow(pool: pg.Pool, followerId: string, target: Target): Promise<void>;
101
+ export declare function isFollowing(pool: pg.Pool, followerId: string, target: Target): Promise<boolean>;
102
+ export declare function followerCount(pool: pg.Pool, target: Target): Promise<number>;
103
+ export interface Following {
104
+ kind: UpdateAuthorKind;
105
+ slug: string | null;
106
+ name: string;
107
+ since: string;
108
+ }
109
+ /** Who one person follows, for their own dashboard. */
110
+ export declare function listFollowing(pool: pg.Pool, followerId: string): Promise<Following[]>;
111
+ /**
112
+ * One author, or everybody.
113
+ *
114
+ * Every representation of the updates takes the same two parameters, so the
115
+ * resolution happens once here rather than four times in four routes drifting
116
+ * apart. `unknown` is a slug nobody has: it is not the same as "no filter",
117
+ * and answering it with the whole board would be answering a question nobody
118
+ * asked.
119
+ */
120
+ export type Scope = {
121
+ kind: 'all';
122
+ } | {
123
+ kind: 'author';
124
+ target: Target;
125
+ author: UpdateAuthorKind;
126
+ slug: string;
127
+ name: string;
128
+ } | {
129
+ kind: 'unknown';
130
+ slug: string;
131
+ };
132
+ export declare function scopeFrom(pool: pg.Pool, params: URLSearchParams): Promise<Scope>;
133
+ /** The updates a scope describes. An unknown author has none, not all. */
134
+ export declare function listScoped(pool: pg.Pool, scope: Scope, limit?: number): Promise<Update[]>;
135
+ /**
136
+ * The page a person's own updates would appear on, if they have one.
137
+ *
138
+ * Posting as yourself requires a listed resume. It is the cheapest useful
139
+ * gate: an update whose author has no page is an anonymous post, and an
140
+ * account made this morning has nothing to lose by spamming from one.
141
+ */
142
+ export declare function candidateSlugFor(pool: pg.Pool, userId: string): Promise<string | null>;
143
+ /**
144
+ * The user behind a public resume slug.
145
+ *
146
+ * Following a candidate follows the person, not the document, so a resume
147
+ * rewritten or replaced does not drop their followers.
148
+ */
149
+ export declare function userForCandidate(pool: pg.Pool, slug: string): Promise<string | null>;