-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
103 lines (92 loc) · 3.82 KB
/
Copy pathindex.ts
File metadata and controls
103 lines (92 loc) · 3.82 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* Notification fan-out for help-wanted side-effects.
*
* v1 ships with email + slack-DM channels; Slack DM is stubbed until the
* Slack integration exists. Failures are logged but never fail the request —
* the spec says express-interest returns 202 to the caller regardless.
*
* The Resend / email transport is also stubbed; this module exists so the
* surface is in place for write-api to call and for tests to spy on.
*/
import type { FastifyBaseLogger } from 'fastify';
export interface HelpWantedInterestNotification {
readonly maintainerEmail: string | null;
readonly maintainerSlackHandle: string | null;
readonly roleTitle: string;
readonly projectTitle: string;
readonly projectSlug: string;
readonly roleId: string;
readonly interestedPersonFullName: string;
readonly interestedPersonSlug: string;
readonly message: string | null;
}
export interface HelpWantedFillNotification {
readonly maintainerEmail: string | null;
readonly roleTitle: string;
readonly projectTitle: string;
readonly filledByFullName: string | null;
readonly filledBySlug: string | null;
}
/**
* Welcome notification — fires once per Person on the GitHub OAuth
* `create-fresh` outcome (a brand-new signup with no laddr-account-claim
* candidates). The email comes from the new PrivateProfile; fullName and
* slug from the public Person record.
*/
export interface WelcomeNotification {
readonly email: string;
readonly fullName: string;
readonly slug: string;
}
/**
* Password-reset notification — fires from `POST /api/auth/password-reset/request`
* when the requested user resolves to a Person with an email on file. The
* `token` is the plaintext (the private store keeps only its hash); it
* leaves the system only via this email send.
*/
export interface PasswordResetNotification {
readonly email: string;
readonly fullName: string;
readonly slug: string;
readonly token: string;
/** Expiry timestamp (ISO 8601), surfaced in the email body. */
readonly expiresAt: string;
}
export interface Notifier {
notifyHelpWantedInterest(n: HelpWantedInterestNotification): Promise<{ delivered: boolean }>;
notifyHelpWantedFilled(n: HelpWantedFillNotification): Promise<{ delivered: boolean }>;
notifyWelcomeOnSignup(n: WelcomeNotification): Promise<{ delivered: boolean }>;
notifyPasswordReset(n: PasswordResetNotification): Promise<{ delivered: boolean }>;
}
/**
* Default no-op notifier — logs the intent and returns delivered:true.
* Replace with a real notifier once the Resend / Slack transports land.
*/
export class LoggingNotifier implements Notifier {
readonly #log: FastifyBaseLogger;
constructor(log: FastifyBaseLogger) {
this.#log = log;
}
async notifyHelpWantedInterest(n: HelpWantedInterestNotification): Promise<{ delivered: boolean }> {
this.#log.info({ kind: 'help-wanted.interest', ...n }, 'help-wanted interest notification');
return { delivered: true };
}
async notifyHelpWantedFilled(n: HelpWantedFillNotification): Promise<{ delivered: boolean }> {
this.#log.info({ kind: 'help-wanted.filled', ...n }, 'help-wanted fill notification');
return { delivered: true };
}
async notifyWelcomeOnSignup(n: WelcomeNotification): Promise<{ delivered: boolean }> {
this.#log.info({ kind: 'auth.welcome', ...n }, 'welcome notification');
return { delivered: true };
}
async notifyPasswordReset(n: PasswordResetNotification): Promise<{ delivered: boolean }> {
// Log the slug + email but NOT the token — even in dev logs, we don't
// want plaintext password-reset tokens persisted anywhere. The token
// is visible via the live email channel only.
this.#log.info(
{ kind: 'auth.password-reset', email: n.email, slug: n.slug, expiresAt: n.expiresAt },
'password-reset notification (token redacted)',
);
return { delivered: true };
}
}