@snagset/server 0.1.15 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +9 -10
- package/dist/app.js +52 -48
- package/dist/app.js.map +1 -1
- package/dist/bin.js +31 -32
- package/dist/bin.js.map +1 -1
- package/dist/boot.js +21 -21
- package/dist/boot.js.map +1 -1
- package/dist/config.generated.js +6 -8
- package/dist/config.generated.js.map +1 -1
- package/dist/config.js +14 -14
- package/dist/config.js.map +1 -1
- package/dist/crypto.js +1 -1
- package/dist/crypto.js.map +1 -1
- package/dist/db/connect.js +1 -1
- package/dist/db/migrate.js +11 -11
- package/dist/db/rls.js +3 -3
- package/dist/db/rls.js.map +1 -1
- package/dist/domain.js +73 -5
- package/dist/domain.js.map +1 -1
- package/dist/explain.js +1 -1
- package/dist/instance/address.js +3 -3
- package/dist/instance/address.js.map +1 -1
- package/dist/instance/settings.js +1 -1
- package/dist/integrations/github.js +3 -3
- package/dist/integrations/github.js.map +1 -1
- package/dist/integrations/inbound.js +3 -3
- package/dist/integrations/inbound.js.map +1 -1
- package/dist/integrations/linear.js +1 -1
- package/dist/integrations/linear.js.map +1 -1
- package/dist/integrations/outbox.js +4 -4
- package/dist/integrations/outbox.js.map +1 -1
- package/dist/integrations/slack.js +2 -2
- package/dist/integrations/slack.js.map +1 -1
- package/dist/jobs/index.js +3 -3
- package/dist/jobs/reaper.js +11 -11
- package/dist/jobs/reaper.js.map +1 -1
- package/dist/jobs/set.js +3 -3
- package/dist/jobs/set.js.map +1 -1
- package/dist/jobs/soon.js +3 -3
- package/dist/keys.js +3 -3
- package/dist/keys.js.map +1 -1
- package/dist/listen.js +3 -3
- package/dist/listen.js.map +1 -1
- package/dist/mail/assigned.js +328 -0
- package/dist/mail/assigned.js.map +1 -0
- package/dist/mail/digest.js +3 -3
- package/dist/mail/digest.js.map +1 -1
- package/dist/mail/index.js +2 -2
- package/dist/mail/links.js +5 -5
- package/dist/mail/mailer.js +2 -2
- package/dist/mail/notify.js +85 -20
- package/dist/mail/notify.js.map +1 -1
- package/dist/mail/resend.js +1 -1
- package/dist/mail/resolve.js +1 -1
- package/dist/mail/resolve.js.map +1 -1
- package/dist/mail/smtp.js +1 -1
- package/dist/mail/unsubscribe.js +1 -1
- package/dist/net/publicFetch.js +1 -1
- package/dist/orgs.js +5 -5
- package/dist/orgs.js.map +1 -1
- package/dist/plan.js +1 -1
- package/dist/projects.js +4 -4
- package/dist/projects.js.map +1 -1
- package/dist/routes/account.js +1 -1
- package/dist/routes/account.js.map +1 -1
- package/dist/routes/activity.js +1 -1
- package/dist/routes/anchorReports.js +9 -9
- package/dist/routes/anchorReports.js.map +1 -1
- package/dist/routes/assignees.js +4 -4
- package/dist/routes/assignees.js.map +1 -1
- package/dist/routes/attachments.js +6 -6
- package/dist/routes/attachments.js.map +1 -1
- package/dist/routes/auth.js +4 -4
- package/dist/routes/bodyLimit.js +5 -5
- package/dist/routes/bodyLimit.js.map +1 -1
- package/dist/routes/commentEdit.js +5 -5
- package/dist/routes/commentEdit.js.map +1 -1
- package/dist/routes/comments.js +24 -24
- package/dist/routes/comments.js.map +1 -1
- package/dist/routes/cors.js +33 -15
- package/dist/routes/cors.js.map +1 -1
- package/dist/routes/instance.js +3 -3
- package/dist/routes/instance.js.map +1 -1
- package/dist/routes/integrations.js +14 -11
- package/dist/routes/integrations.js.map +1 -1
- package/dist/routes/invites.js +7 -7
- package/dist/routes/invites.js.map +1 -1
- package/dist/routes/mcpAuth.js +13 -13
- package/dist/routes/mcpAuth.js.map +1 -1
- package/dist/routes/mcpToken.js +4 -4
- package/dist/routes/mcpToken.js.map +1 -1
- package/dist/routes/mcpTokens.js +27 -14
- package/dist/routes/mcpTokens.js.map +1 -1
- package/dist/routes/members.js +9 -5
- package/dist/routes/members.js.map +1 -1
- package/dist/routes/owner.js +13 -13
- package/dist/routes/owner.js.map +1 -1
- package/dist/routes/reviewLinks.js +4 -4
- package/dist/routes/reviewLinks.js.map +1 -1
- package/dist/routes/rounds.js +3 -3
- package/dist/routes/rounds.js.map +1 -1
- package/dist/routes/sessions.js +186 -71
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/sites.js +9 -9
- package/dist/routes/sites.js.map +1 -1
- package/dist/routes/threads.js +144 -90
- package/dist/routes/threads.js.map +1 -1
- package/dist/routes/widget.js +5 -5
- package/dist/routes/widget.js.map +1 -1
- package/dist/serverless.js +4 -4
- package/dist/serverless.js.map +1 -1
- package/dist/session.js +8 -8
- package/dist/session.js.map +1 -1
- package/dist/storage/bucket.js +4 -4
- package/dist/storage/bucket.js.map +1 -1
- package/dist/storage/settings.js +4 -4
- package/dist/storage/settings.js.map +1 -1
- package/dist/studioReviewers.js +4 -4
- package/dist/vendor/adapters/outbound/fence.js +2 -2
- package/dist/vendor/adapters/outbound/sanitise.js +14 -14
- package/dist/vendor/mcp/bin.js +4 -4
- package/dist/vendor/mcp/client.js +3 -3
- package/dist/vendor/mcp/server.js +3 -3
- package/dist/vendor/mcp/source.js +13 -13
- package/dist/vendor/mcp/view.js +15 -15
- package/dist/vendor/types/brief.js +2 -2
- package/dist/vendor/types/index.js +1 -1
- package/dist/vendor/types/names.js +20 -18
- package/dist/vendor/widget/object.js +3 -3
- package/dist/vendor/widget/predicates.js +38 -39
- package/package.json +1 -5
- package/static/dashboard/assets/{CompositeItem-DcTtfhMj.js → CompositeItem-D1SCvKo_.js} +1 -1
- package/static/dashboard/assets/{ConfirmDialog-C7IpllNH.js → ConfirmDialog-DdUOWt96.js} +1 -1
- package/static/dashboard/assets/{CopyButton-C0gZH7mu.js → CopyButton-DX7vax3M.js} +1 -1
- package/static/dashboard/assets/{FieldItemContext-D0qd3DQv.js → FieldItemContext-CMpCHNX8.js} +1 -1
- package/static/dashboard/assets/{InstallTag-DTw-EtuD.js → InstallTag-DkYPsEI0.js} +1 -1
- package/static/dashboard/assets/{SaveState-Bk30DUJ3.js → SaveState-D0Hl8cny.js} +1 -1
- package/static/dashboard/assets/{_app-CAmVh6lR.js → _app-CcUZ9WT2.js} +1 -1
- package/static/dashboard/assets/{_app-gSqPuozu.js → _app-CnG-wKIX.js} +1 -1
- package/static/dashboard/assets/{activity-zmYVgnQm.js → activity-DnvsedIb.js} +1 -1
- package/static/dashboard/assets/{arrow-left-CqsX1hoI.js → arrow-left-4jbTF1bA.js} +1 -1
- package/static/dashboard/assets/{avatar-08tu7BM9.js → avatar-BKSc7tnv.js} +1 -1
- package/static/dashboard/assets/{badge-CgsMqJa4.js → badge-aWVtPffe.js} +1 -1
- package/static/dashboard/assets/{card-CPsX9FTO.js → card-Bz0VdsxC.js} +1 -1
- package/static/dashboard/assets/{check-CJRIq1X0.js → check-Ti2IOlgc.js} +1 -1
- package/static/dashboard/assets/{chevron-right-BEmxTSY1.js → chevron-right-g-zbIopD.js} +1 -1
- package/static/dashboard/assets/{copy-xpnXaoOA.js → copy-BwBuD0FO.js} +1 -1
- package/static/dashboard/assets/{dialog-CxlCupuA.js → dialog-DdpHZobU.js} +2 -2
- package/static/dashboard/assets/{dropdown-menu-CGMdCM3V.js → dropdown-menu-CB2L3nig.js} +1 -1
- package/static/dashboard/assets/{image-off-XJf-5RS9.js → image-off-w_ej9pIJ.js} +1 -1
- package/static/dashboard/assets/index-DAgnfv_E.css +1 -0
- package/static/dashboard/assets/{index-CR63M02l.js → index-DtF8YHqn.js} +3 -3
- package/static/dashboard/assets/{invite-CcYboRQr.js → invite-Dy3iQjzf.js} +1 -1
- package/static/dashboard/assets/{label-Ds2N_0qv.js → label-Dj8L94iS.js} +1 -1
- package/static/dashboard/assets/{layout-grid-Dw9tiVXr.js → layout-grid-DuweH0Vl.js} +1 -1
- package/static/dashboard/assets/me-7-r9OeMR.js +1 -0
- package/static/dashboard/assets/{parts-BYKOSeUb.js → parts-CA4cbfy3.js} +1 -1
- package/static/dashboard/assets/{plus-DvfThULG.js → plus-CEWgXDLj.js} +1 -1
- package/static/dashboard/assets/s._site-D69M50OI.js +1 -0
- package/static/dashboard/assets/{s._site.comments-DTMz620W.js → s._site.comments-DNJ2SE5c.js} +1 -1
- package/static/dashboard/assets/{s._site.reviewers-CDhFQ_du.js → s._site.reviewers-B07NzSWj.js} +1 -1
- package/static/dashboard/assets/{s._site.settings-BWE3eC3w.js → s._site.settings-B577G5iG.js} +1 -1
- package/static/dashboard/assets/{setup-BP-kGzjw.js → setup-DcjjtB3g.js} +1 -1
- package/static/dashboard/assets/{sign-in-Brn6kGrC.js → sign-in-gfvOs6Jw.js} +1 -1
- package/static/dashboard/assets/{sign-up-DxeJ0RND.js → sign-up-CFJt7ibH.js} +1 -1
- package/static/dashboard/assets/{sites-BSP6CMHL.js → sites-DbB5cjz5.js} +1 -1
- package/static/dashboard/assets/studio-BO-MHFqc.js +1 -0
- package/static/dashboard/assets/{studio-BqA_XZwz.js → studio-D9GxJaSF.js} +1 -1
- package/static/dashboard/assets/{studio.index-CjkyAcKF.js → studio.index-DktAxIPT.js} +1 -1
- package/static/dashboard/assets/studio.integrations-CbLyqPLX.js +1 -0
- package/static/dashboard/assets/{studio.members-CECLI69a.js → studio.members-DFtIL3JS.js} +1 -1
- package/static/dashboard/assets/{sun-B5BhapBo.js → sun-Bg7U-6Y0.js} +1 -1
- package/static/dashboard/assets/{switch-C4RnvllL.js → switch-CTN6hIca.js} +1 -1
- package/static/dashboard/assets/{table-DOCWCO9W.js → table-zQ3pj50_.js} +1 -1
- package/static/dashboard/assets/{useAriaLabelledBy-C6VUMzW5.js → useAriaLabelledBy-BsNncGbx.js} +1 -1
- package/static/dashboard/assets/{useBaseQuery-ldF9uyzz.js → useBaseQuery-DSqwWD_O.js} +1 -1
- package/static/dashboard/assets/useQuery-OgrKuHbc.js +1 -0
- package/static/dashboard/assets/useRender-DOFVvGfR.js +1 -0
- package/static/dashboard/assets/{useValueChanged-BVjcYor6.js → useValueChanged-B9k7l-mp.js} +1 -1
- package/static/dashboard/assets/{users-Ji1BLjAt.js → users-CRiOyD0f.js} +1 -1
- package/static/dashboard/assets/{welcome-DzAUdMme.js → welcome-DT2DB4jN.js} +1 -1
- package/static/dashboard/assets/{widget-signin-CKUvfq3K.js → widget-signin-CC9owVvs.js} +1 -1
- package/static/dashboard/assets/{you-2QsQK86v.js → you-CtcXklc_.js} +1 -1
- package/static/dashboard/assets/{zod-D-eI47yt.js → zod-CLxHj89d.js} +1 -1
- package/static/dashboard/index.html +2 -2
- package/static/widget/snag.capture.js +1 -1
- package/static/widget/snag.dock.js +5 -5
- package/static/widget/snag.runtime.js +2 -2
- package/static/widget/snag.screenshot.js +1 -1
- package/static/dashboard/assets/index-Ddi7bl87.css +0 -1
- package/static/dashboard/assets/me-BbIUR0d4.js +0 -1
- package/static/dashboard/assets/s._site-D7IqF1qB.js +0 -1
- package/static/dashboard/assets/studio-BW7OdRP1.js +0 -1
- package/static/dashboard/assets/studio.integrations-NDAXAPKG.js +0 -1
- package/static/dashboard/assets/useQuery-VzaemGSO.js +0 -1
- package/static/dashboard/assets/useRender-CRLtPZ-q.js +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { sanitise, sanitiseWithRedactor } from "../vendor/adapters/index.js";
|
|
3
|
+
import { redactText } from "../vendor/widget/predicates.js";
|
|
4
|
+
import { noteQueued } from "../jobs/soon.js";
|
|
5
|
+
import { threadUrl } from "./links.js";
|
|
6
|
+
/**
|
|
7
|
+
* "This snag is yours now", and deliberately NOT sent at the click.
|
|
8
|
+
*
|
|
9
|
+
* Pastel mails the assignee the moment they are picked, and the maintainer's brief
|
|
10
|
+
* was the opposite: assignment is something a studio does in bursts — triage
|
|
11
|
+
* a round, hand out twelve snags, change its mind about three of them — and
|
|
12
|
+
* twelve mails in a minute, three of them about work that has since moved to
|
|
13
|
+
* somebody else, teach the recipient to filter the sender before the one that
|
|
14
|
+
* matters arrives. So the mail WAITS, and when it goes it goes once.
|
|
15
|
+
*
|
|
16
|
+
* ── THREE RULES, ONE ROW EACH ──
|
|
17
|
+
*
|
|
18
|
+
* 1. **Late.** The row is queued in the PATCH's transaction (as
|
|
19
|
+
* every outbox row is) with `next_attempt_at = now() + ASSIGN_DELAY`.
|
|
20
|
+
* An hour, as a constant rather than a variable for the reason
|
|
21
|
+
* `PER_ADDRESS_HOURLY` gives in `notify.ts`: a number nobody has asked to
|
|
22
|
+
* change is not worth a line an operator has to read past.
|
|
23
|
+
* 2. **Still true.** At send time the row is checked again and DROPPED —
|
|
24
|
+
* with a reason in `last_error`, the status the design has for "nothing to
|
|
25
|
+
* send" — when the snag is no longer theirs, is resolved or deleted, or
|
|
26
|
+
* they have clearly seen it: commented on it, or changed its status,
|
|
27
|
+
* after the assignment. The status half is decided at the PATCH that
|
|
28
|
+
* changes it (`dropAssignedBy`), because nothing records WHO moved a
|
|
29
|
+
* status and the moment of the move is the one place that knows.
|
|
30
|
+
* 3. **Together.** The delivery job claims one row as it always has; when
|
|
31
|
+
* that row is an assignment, every other DUE assignment for the same
|
|
32
|
+
* address in the same studio is claimed with it and the lot goes as one
|
|
33
|
+
* mail — "3 snags assigned to you on Acme" — and all of them are
|
|
34
|
+
* marked `done` together. Across sites, too: a studio member has one
|
|
35
|
+
* reviewer row per site, and the address is what makes them one person.
|
|
36
|
+
*
|
|
37
|
+
* ── WHO IS NEVER QUEUED ──
|
|
38
|
+
*
|
|
39
|
+
* The person who assigned it to themselves (by reviewer row, by dashboard
|
|
40
|
+
* user, or by address — `samePerson` below, the rule); anyone with no
|
|
41
|
+
* address; anyone who unsubscribed, was blocked or erased; a studio member
|
|
42
|
+
* whose user was deleted. All decided at enqueue AND again at send, because
|
|
43
|
+
* an hour is long enough for any of them to change.
|
|
44
|
+
*
|
|
45
|
+
* ── THE DEDUPE KEY ──
|
|
46
|
+
*
|
|
47
|
+
* `thread.assigned:<threadId>:<threads.version>:<reviewerId>` — the
|
|
48
|
+
* `<topic>:<entityId>:<version>` composition with the recipient appended, as
|
|
49
|
+
* the fan-out does. The version is what lets a snag assigned, taken away and
|
|
50
|
+
* given back NEXT WEEK be a new mail; it is not what stops the same person
|
|
51
|
+
* being queued twice inside the hour. That is the push-back: a PENDING row
|
|
52
|
+
* for this thread and this person is moved to an hour from now instead of a
|
|
53
|
+
* second row being written. The thread's row lock, held by the PATCH's own
|
|
54
|
+
* UPDATE, serialises two assigners racing on one thread, so the look-then-
|
|
55
|
+
* insert cannot double.
|
|
56
|
+
*/
|
|
57
|
+
export const ASSIGNED_TOPIC = "thread.assigned";
|
|
58
|
+
/** How long an assignment waits before it is worth a mail. */
|
|
59
|
+
/** The default wait; `SNAGSET_ASSIGN_MAIL_DELAY_S` overrides it. */
|
|
60
|
+
export const ASSIGN_DELAY_MS = 60 * 60_000;
|
|
61
|
+
/** How many snags one mail LISTS — the digest's reason (event size, attention). */
|
|
62
|
+
export const ASSIGNED_LISTED = 50;
|
|
63
|
+
const NAME_MAX = 60;
|
|
64
|
+
const EXCERPT_MAX = 140;
|
|
65
|
+
/**
|
|
66
|
+
* The SQL test "is reviewer `r` this actor?" — by row, by dashboard user, or
|
|
67
|
+
* by address, case-insensitively. The rule, in the form the queries below
|
|
68
|
+
* need. A session's row is expanded to its user and address first
|
|
69
|
+
* (`expand`), so a member signed in through the widget is the same person as
|
|
70
|
+
* their dashboard self.
|
|
71
|
+
*/
|
|
72
|
+
function matches(tx, alias, a) {
|
|
73
|
+
const r = tx(alias);
|
|
74
|
+
return tx `(
|
|
75
|
+
${a.reviewerId ? tx `${r}.id = ${a.reviewerId}::uuid` : tx `false`}
|
|
76
|
+
OR ${a.userId ? tx `${r}.user_id = ${a.userId}::uuid` : tx `false`}
|
|
77
|
+
OR ${a.email ? tx `lower(${r}.email) = lower(${a.email})` : tx `false`}
|
|
78
|
+
)`;
|
|
79
|
+
}
|
|
80
|
+
/** A session's reviewer row → the user and address behind it, when it has them. */
|
|
81
|
+
async function expand(tx, a) {
|
|
82
|
+
if (!a.reviewerId || (a.userId && a.email))
|
|
83
|
+
return a;
|
|
84
|
+
const [r] = await tx `
|
|
85
|
+
SELECT user_id, email FROM reviewers WHERE id = ${a.reviewerId}::uuid`;
|
|
86
|
+
return {
|
|
87
|
+
reviewerId: a.reviewerId,
|
|
88
|
+
userId: a.userId ?? r?.user_id ?? undefined,
|
|
89
|
+
email: a.email ?? r?.email ?? undefined,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Queue (or push back) one assignment mail, INSIDE the PATCH's transaction.
|
|
94
|
+
* Returns whether a row now waits, for the tests and nothing else.
|
|
95
|
+
*/
|
|
96
|
+
export async function queueAssignedNotice(tx, n) {
|
|
97
|
+
const actor = await expand(tx, n.actor);
|
|
98
|
+
/**
|
|
99
|
+
* The assignee, deliverable or not. The studio member's address is the
|
|
100
|
+
* USER's, read now: the reviewer row copied it when it was materialised and
|
|
101
|
+
* a user who has since changed theirs should be mailed where they read.
|
|
102
|
+
*/
|
|
103
|
+
const [to] = await tx `
|
|
104
|
+
SELECT r.id, ${matches(tx, "r", actor)} AS is_actor
|
|
105
|
+
FROM reviewers r
|
|
106
|
+
LEFT JOIN users u ON u.id = r.user_id
|
|
107
|
+
WHERE r.id = ${n.assigneeId}::uuid
|
|
108
|
+
AND r.notifications_off_at IS NULL
|
|
109
|
+
AND r.blocked_at IS NULL
|
|
110
|
+
AND r.erased_at IS NULL
|
|
111
|
+
AND (r.user_id IS NULL OR u.deleted_at IS NULL)
|
|
112
|
+
AND COALESCE(u.email, r.email) IS NOT NULL`;
|
|
113
|
+
// Nobody to write to, or the assigner picked themselves: no row at all —
|
|
114
|
+
// the row never written is the one that cannot be sent.
|
|
115
|
+
if (!to || to.is_actor)
|
|
116
|
+
return false;
|
|
117
|
+
const event = {
|
|
118
|
+
threadKey: n.threadKey,
|
|
119
|
+
actorName: n.actorName,
|
|
120
|
+
toReviewer: n.assigneeId,
|
|
121
|
+
};
|
|
122
|
+
// Already waiting for this person on this thread: wait an hour from NOW
|
|
123
|
+
// instead, and remember the newer assignment time — "has since commented"
|
|
124
|
+
// is measured from the assignment the mail is about.
|
|
125
|
+
const [pushed] = await tx `
|
|
126
|
+
UPDATE outbox
|
|
127
|
+
SET next_attempt_at = now() + ${`${n.delayMs ?? ASSIGN_DELAY_MS} milliseconds`}::interval,
|
|
128
|
+
event = event || jsonb_build_object('assignedAt', now(), 'actorName', ${n.actorName}::text)
|
|
129
|
+
WHERE provider = 'email'
|
|
130
|
+
AND topic = ${ASSIGNED_TOPIC}
|
|
131
|
+
AND thread_id = ${n.threadId}::uuid
|
|
132
|
+
AND status = 'pending'
|
|
133
|
+
AND event->>'toReviewer' = ${n.assigneeId}
|
|
134
|
+
RETURNING id`;
|
|
135
|
+
if (pushed)
|
|
136
|
+
return true;
|
|
137
|
+
await tx `
|
|
138
|
+
INSERT INTO outbox (id, org_id, project_id, thread_id, provider, topic, dedupe_key, event, next_attempt_at)
|
|
139
|
+
VALUES (
|
|
140
|
+
${randomUUID()}, ${n.orgId}, ${n.projectId}, ${n.threadId},
|
|
141
|
+
'email', ${ASSIGNED_TOPIC},
|
|
142
|
+
${`${ASSIGNED_TOPIC}:${n.threadId}:${n.version}:${n.assigneeId}`},
|
|
143
|
+
${tx.json(event)}::jsonb || jsonb_build_object('assignedAt', now()),
|
|
144
|
+
now() + ${`${n.delayMs ?? ASSIGN_DELAY_MS} milliseconds`}::interval
|
|
145
|
+
)
|
|
146
|
+
ON CONFLICT (provider, dedupe_key) DO NOTHING`;
|
|
147
|
+
// A drain after the response finds nothing due for an hour — and
|
|
148
|
+
// then whatever else this write queued goes, which is the point of it.
|
|
149
|
+
noteQueued();
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The assignee moved the status themselves: they have seen it, and the mail
|
|
154
|
+
* that would tell them is dropped now. Called by the PATCH that changes a
|
|
155
|
+
* status, with whoever is pressing the button.
|
|
156
|
+
*/
|
|
157
|
+
export async function dropAssignedBy(tx, threadId, actor) {
|
|
158
|
+
const who = await expand(tx, actor);
|
|
159
|
+
await tx `
|
|
160
|
+
UPDATE outbox o
|
|
161
|
+
SET status = 'dropped', last_error = 'the assignee changed its status', completed_at = now()
|
|
162
|
+
FROM reviewers r
|
|
163
|
+
WHERE o.provider = 'email'
|
|
164
|
+
AND o.topic = ${ASSIGNED_TOPIC}
|
|
165
|
+
AND o.thread_id = ${threadId}::uuid
|
|
166
|
+
AND o.status = 'pending'
|
|
167
|
+
AND r.id = CASE WHEN o.event->>'toReviewer' ~ '^[0-9a-fA-F-]{36}$'
|
|
168
|
+
THEN (o.event->>'toReviewer')::uuid END
|
|
169
|
+
AND ${matches(tx, "r", who)}`;
|
|
170
|
+
}
|
|
171
|
+
/** The reviewer an assignment row is for, if they can still be mailed. */
|
|
172
|
+
export async function assignedRecipient(sql, row) {
|
|
173
|
+
const id = String(row.event["toReviewer"] ?? "");
|
|
174
|
+
if (!/^[0-9a-fA-F-]{36}$/.test(id))
|
|
175
|
+
return null;
|
|
176
|
+
const [r] = await sql `
|
|
177
|
+
SELECT r.id, r.user_id, COALESCE(u.email, r.email) AS email
|
|
178
|
+
FROM reviewers r
|
|
179
|
+
LEFT JOIN users u ON u.id = r.user_id
|
|
180
|
+
WHERE r.id = ${id}::uuid
|
|
181
|
+
AND r.notifications_off_at IS NULL
|
|
182
|
+
AND r.blocked_at IS NULL
|
|
183
|
+
AND r.erased_at IS NULL
|
|
184
|
+
AND (r.user_id IS NULL OR u.deleted_at IS NULL)`;
|
|
185
|
+
return r?.email ? { reviewerId: r.id, userId: r.user_id, email: r.email } : null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Every OTHER due assignment for the same address in the same studio,
|
|
189
|
+
* claimed (`inflight`, attempts + 1) exactly as the delivery job claims its
|
|
190
|
+
* one row — `SKIP LOCKED`, so a second instance draining at once takes what
|
|
191
|
+
* this one did not. Each candidate's own recipient is re-derived here, so a
|
|
192
|
+
* row whose reviewer unsubscribed since is simply not part of the batch and
|
|
193
|
+
* is dropped by its own claim later.
|
|
194
|
+
*/
|
|
195
|
+
export async function claimSiblings(sql, first, email, now) {
|
|
196
|
+
return sql `
|
|
197
|
+
UPDATE outbox SET status = 'inflight', attempts = attempts + 1
|
|
198
|
+
WHERE id IN (
|
|
199
|
+
SELECT o.id
|
|
200
|
+
FROM outbox o
|
|
201
|
+
JOIN reviewers r ON r.id = CASE WHEN o.event->>'toReviewer' ~ '^[0-9a-fA-F-]{36}$'
|
|
202
|
+
THEN (o.event->>'toReviewer')::uuid END
|
|
203
|
+
LEFT JOIN users u ON u.id = r.user_id
|
|
204
|
+
WHERE o.provider = 'email'
|
|
205
|
+
AND o.topic = ${ASSIGNED_TOPIC}
|
|
206
|
+
AND o.status = 'pending'
|
|
207
|
+
AND o.org_id = ${first.org_id}
|
|
208
|
+
AND o.id <> ${first.id}
|
|
209
|
+
AND o.next_attempt_at <= ${now}
|
|
210
|
+
AND r.notifications_off_at IS NULL
|
|
211
|
+
AND r.blocked_at IS NULL
|
|
212
|
+
AND r.erased_at IS NULL
|
|
213
|
+
AND lower(COALESCE(u.email, r.email)) = lower(${email})
|
|
214
|
+
ORDER BY o.next_attempt_at
|
|
215
|
+
LIMIT ${ASSIGNED_LISTED - 1}
|
|
216
|
+
FOR UPDATE OF o SKIP LOCKED
|
|
217
|
+
)
|
|
218
|
+
RETURNING id, org_id, attempts, event`;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Split claimed rows into what is still news and what is not, with the reason
|
|
222
|
+
* for each drop. Read at SEND time, which is the point of waiting an hour.
|
|
223
|
+
*/
|
|
224
|
+
export async function liveAssignments(sql, rows) {
|
|
225
|
+
const live = [];
|
|
226
|
+
const dropped = [];
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
const to = String(row.event["toReviewer"] ?? "");
|
|
229
|
+
const at = String(row.event["assignedAt"] ?? "") || "-infinity";
|
|
230
|
+
const [t] = await sql `
|
|
231
|
+
SELECT t.key, t.status::text AS status,
|
|
232
|
+
(t.deleted_at IS NOT NULL OR p.deleted_at IS NOT NULL) AS gone,
|
|
233
|
+
t.assignee_id, p.name AS site, p.uid, rd.number AS round,
|
|
234
|
+
(SELECT c.body FROM comments c
|
|
235
|
+
WHERE c.thread_id = t.id AND c.deleted_at IS NULL
|
|
236
|
+
ORDER BY c.seq LIMIT 1) AS first,
|
|
237
|
+
-- "They have clearly seen it": a comment by the same person —
|
|
238
|
+
-- this row, their dashboard self, or their address — written
|
|
239
|
+
-- after the assignment the mail is about.
|
|
240
|
+
EXISTS (
|
|
241
|
+
SELECT 1 FROM comments c
|
|
242
|
+
JOIN reviewers cr ON cr.id = c.author_id
|
|
243
|
+
JOIN reviewers me ON me.id = ${to}::uuid
|
|
244
|
+
WHERE c.thread_id = t.id
|
|
245
|
+
AND c.deleted_at IS NULL
|
|
246
|
+
AND c.created_at > ${at}::timestamptz
|
|
247
|
+
AND (cr.id = me.id
|
|
248
|
+
OR (me.user_id IS NOT NULL AND cr.user_id = me.user_id)
|
|
249
|
+
OR (me.email IS NOT NULL AND lower(cr.email) = lower(me.email)))
|
|
250
|
+
) AS seen
|
|
251
|
+
FROM outbox o
|
|
252
|
+
JOIN threads t ON t.id = o.thread_id
|
|
253
|
+
JOIN projects p ON p.id = t.project_id
|
|
254
|
+
LEFT JOIN rounds rd ON rd.id = t.round_id
|
|
255
|
+
WHERE o.id = ${row.id}::uuid`;
|
|
256
|
+
const reason = !t
|
|
257
|
+
? "the thread was deleted"
|
|
258
|
+
: t.gone
|
|
259
|
+
? "the thread was deleted"
|
|
260
|
+
: t.assignee_id !== to
|
|
261
|
+
? "the thread is no longer assigned to them"
|
|
262
|
+
: t.status === "resolved"
|
|
263
|
+
? "the thread was resolved"
|
|
264
|
+
: t.seen
|
|
265
|
+
? "the assignee has replied since"
|
|
266
|
+
: null;
|
|
267
|
+
if (reason || !t) {
|
|
268
|
+
dropped.push({ id: row.id, reason: reason ?? "the thread was deleted" });
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
live.push({
|
|
272
|
+
rowId: row.id,
|
|
273
|
+
threadKey: t.key,
|
|
274
|
+
site: t.site,
|
|
275
|
+
siteUid: t.uid,
|
|
276
|
+
round: t.round,
|
|
277
|
+
actorName: String(row.event["actorName"] ?? "Someone"),
|
|
278
|
+
// Redacted BEFORE clamping, as the digest does: half a token is one the
|
|
279
|
+
// redactor no longer recognises.
|
|
280
|
+
excerpt: sanitiseWithRedactor((t.first ?? "").split("\n")[0] ?? "", "plain", redactText, {
|
|
281
|
+
singleLine: true,
|
|
282
|
+
max: EXCERPT_MAX,
|
|
283
|
+
}),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return { live, dropped };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The mail. Text only, as every mail here is. One snag reads as a sentence;
|
|
290
|
+
* several read as a list, grouped by site, each with its key, the first line
|
|
291
|
+
* of what was said, and the link that opens it.
|
|
292
|
+
*/
|
|
293
|
+
export function renderAssigned(items, to, baseUrl) {
|
|
294
|
+
const plain = (s, max) => sanitise(s, "plain", { singleLine: true, max });
|
|
295
|
+
const sites = [...new Set(items.map((i) => i.siteUid))];
|
|
296
|
+
const siteName = plain(items[0].site, NAME_MAX);
|
|
297
|
+
const n = items.length;
|
|
298
|
+
if (n === 1) {
|
|
299
|
+
const it = items[0];
|
|
300
|
+
const key = plain(it.threadKey, 24);
|
|
301
|
+
return {
|
|
302
|
+
to,
|
|
303
|
+
subject: `${key} was assigned to you on ${siteName}`,
|
|
304
|
+
text: [
|
|
305
|
+
`${plain(it.actorName, NAME_MAX)} assigned ${key} to you on ${siteName}.`,
|
|
306
|
+
"",
|
|
307
|
+
...(it.excerpt ? [`“${plain(it.excerpt, EXCERPT_MAX + 1)}”`, ""] : []),
|
|
308
|
+
`Open it: ${threadUrl(baseUrl, it.siteUid, it.threadKey, it.round)}`,
|
|
309
|
+
"",
|
|
310
|
+
"— Snagset",
|
|
311
|
+
"Sent an hour after the assignment, unless it moved on or you had already answered it.",
|
|
312
|
+
].join("\n"),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const subject = sites.length === 1 ? `${n} snags assigned to you on ${siteName}` : `${n} snags assigned to you across ${sites.length} sites`;
|
|
316
|
+
const lines = [`${n} snags were assigned to you.`, ""];
|
|
317
|
+
for (const uid of sites) {
|
|
318
|
+
const mine = items.filter((i) => i.siteUid === uid);
|
|
319
|
+
lines.push(`${plain(mine[0].site, NAME_MAX)} (${mine.length})`);
|
|
320
|
+
for (const it of mine) {
|
|
321
|
+
lines.push(` ${plain(it.threadKey, 24)} · from ${plain(it.actorName, NAME_MAX)}${it.excerpt ? `: ${plain(it.excerpt, EXCERPT_MAX + 1)}` : ""}`, ` ${threadUrl(baseUrl, it.siteUid, it.threadKey, it.round)}`);
|
|
322
|
+
}
|
|
323
|
+
lines.push("");
|
|
324
|
+
}
|
|
325
|
+
lines.push("— Snagset", "Assignments are gathered for an hour and sent together, leaving out any that moved on or that you had already answered.");
|
|
326
|
+
return { to, subject, text: lines.join("\n") };
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=assigned.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assigned.js","sourceRoot":"","sources":["../../src/mail/assigned.ts"],"names":[],"mappings":"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"}
|
package/dist/mail/digest.js
CHANGED
|
@@ -4,7 +4,7 @@ import { redactText } from "../vendor/widget/predicates.js";
|
|
|
4
4
|
import { isConfigured } from "./mailer.js";
|
|
5
5
|
import { dashboardUrl, threadUrl } from "./links.js";
|
|
6
6
|
/**
|
|
7
|
-
* The daily digest
|
|
7
|
+
* The daily digest, on the maintainer's "we don't need to send the email every
|
|
8
8
|
* time there is a comment, maybe once in a while".
|
|
9
9
|
*
|
|
10
10
|
* `SNAGSET_MAIL_NOTIFY=digest` (the default) changes ONE thing: how the
|
|
@@ -54,7 +54,7 @@ import { dashboardUrl, threadUrl } from "./links.js";
|
|
|
54
54
|
* Whatever runs the sweep runs this: the platform's daily cron (03:00 UTC in
|
|
55
55
|
* the scaffold, so the digest arrives then), the bin's hourly ticker (so the
|
|
56
56
|
* first tick after midnight), or `/api/jobs/run` by hand. NOT the drain a
|
|
57
|
-
* write starts
|
|
57
|
+
* write starts: a comment is never the reason to send a digest, and the
|
|
58
58
|
* drain running this would only find the day's window already closed.
|
|
59
59
|
*/
|
|
60
60
|
export const DIGEST_TOPIC = "comment.digest";
|
|
@@ -153,7 +153,7 @@ export function buildDigests(mailer) {
|
|
|
153
153
|
if (rows.length === 0)
|
|
154
154
|
return { did: 0 };
|
|
155
155
|
// A comment deleted before its digest is not news. Dropped, with the
|
|
156
|
-
// status
|
|
156
|
+
// status the design has for "nothing to send", rather than listed.
|
|
157
157
|
const gone = rows.filter((r) => r.gone).map((r) => r.id);
|
|
158
158
|
const live = rows.filter((r) => !r.gone);
|
|
159
159
|
if (gone.length) {
|
package/dist/mail/digest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/mail/digest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC7C,kFAAkF;AAClF,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAChC,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,QAAQ,GAAG,EAAE,CAAC;AA2BpB,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,GAAS;IACpC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,MAAY;IACnD,OAAO,GAAG,YAAY,IAAI,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC;AAcD;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,EAAE,GAAG,MAAM;QACpB,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAc;YAChC,oEAAoE;YACpE,wEAAwE;YACxE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;YAE7C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAE9C,qEAAqE;YACrE,sEAAsE;YACtE,mEAAmE;YACnE,sDAAsD;YACtD,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,CAAsB;;;;2BAIzB,aAAa;;;gCAGR,MAAM;;;;qCAID,GAAG,YAAY,GAAG,+BAA+B,IAAI,GAAG,EAAE;;;iBAG9E,CAAC;YACZ,IAAI,CAAC,IAAI;gBAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;YAE1B;;;;;eAKG;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAc;;;;;;;;;;;;;4BAad,KAAK;;2BAEN,aAAa;;;gCAGR,MAAM;;qCAED,CAAC;YAChC,kEAAkE;YAClE,oEAAoE;YACpE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;YAEzC,qEAAqE;YACrE,
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/mail/digest.ts"],"names":[],"mappings":"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"}
|
package/dist/mail/index.js
CHANGED
|
@@ -22,7 +22,7 @@ export const MAIL_SCHEMES = ["smtp", "smtps", "resend"];
|
|
|
22
22
|
* case in one function and no new configuration at all. An operator moving
|
|
23
23
|
* from their own mail server to a provider edits one line.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
25
|
+
* The other rules, implemented rather than restated:
|
|
26
26
|
*
|
|
27
27
|
* · Unset ⇒ notifications are **off and say so**. Not queued. A feature that
|
|
28
28
|
* is off and visible is an operator's decision; one that queues rows
|
|
@@ -55,7 +55,7 @@ export function selectMailer(cfg) {
|
|
|
55
55
|
/**
|
|
56
56
|
* A transport URL and a From address → a mailer, or a sentence. The ONE parse
|
|
57
57
|
* — the environment (`selectMailer`) and the dashboard's stored transport
|
|
58
|
-
* (`resolve.ts
|
|
58
|
+
* (`resolve.ts`) both come through here, so an unsupported scheme is
|
|
59
59
|
* refused with the same words wherever it was typed.
|
|
60
60
|
*
|
|
61
61
|
* Parsed by hand rather than with `new URL`: a Resend key is the AUTHORITY
|
package/dist/mail/links.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Where a notification's link goes: the thread, open, in the dashboard
|
|
2
|
+
* Where a notification's link goes: the thread, open, in the dashboard.
|
|
3
3
|
*
|
|
4
|
-
* Until
|
|
5
|
-
* served at the ROOT of the address (`app.ts`'s fallback
|
|
4
|
+
* Until every mail linked `<address>/dashboard`, and the dashboard is
|
|
5
|
+
* served at the ROOT of the address (`app.ts`'s fallback), so that link
|
|
6
6
|
* landed on a route the SPA does not have. The comments page has always taken
|
|
7
|
-
* the thread as a search parameter — every view there is a link,
|
|
7
|
+
* the thread as a search parameter — every view there is a link, — and the
|
|
8
8
|
* round with it, because the list shows the latest round unless told
|
|
9
9
|
* otherwise, and a thread from an earlier round would open to nothing.
|
|
10
10
|
*
|
|
@@ -23,7 +23,7 @@ export function dashboardUrl(baseUrl) {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* The link for a row that names a thread, read at SEND time: the round and
|
|
26
|
-
* the site's uid are not in the event, and a row queued before
|
|
26
|
+
* the site's uid are not in the event, and a row queued before the decision has
|
|
27
27
|
* neither. A thread that has gone since falls back to the dashboard rather
|
|
28
28
|
* than to a link that opens nothing.
|
|
29
29
|
*/
|
package/dist/mail/mailer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `Mailer` seam
|
|
2
|
+
* The `Mailer` seam, and built BEFORE the first notification call
|
|
3
3
|
* site exists, which is the entire point of naming it in the lock.
|
|
4
4
|
*
|
|
5
5
|
* *"otherwise the transport gets imported in eight places, and the import lint
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* have to add a vendor to a product whose pitch is that it self-hosts.
|
|
19
19
|
*
|
|
20
20
|
* Neither is required. `DATABASE_URL` remains the only mandatory variable
|
|
21
|
-
*
|
|
21
|
+
* and with no transport configured notifications are OFF AND SAY SO
|
|
22
22
|
* rather than queueing rows nobody will deliver.
|
|
23
23
|
*/
|
|
24
24
|
export function isConfigured(m) {
|