@wopr-network/platform-core 1.55.0 → 1.56.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/dist/auth/better-auth.d.ts +2 -0
- package/dist/auth/better-auth.js +2 -2
- package/dist/email/drizzle-notification-template-repository.d.ts +11 -0
- package/dist/email/drizzle-notification-template-repository.js +18 -0
- package/dist/email/notification-service.d.ts +4 -1
- package/dist/email/notification-service.js +37 -31
- package/dist/email/notification-service.test.js +14 -0
- package/package.json +1 -1
- package/src/auth/better-auth.ts +6 -2
- package/src/email/drizzle-notification-template-repository.ts +27 -0
- package/src/email/notification-service.test.ts +14 -0
- package/src/email/notification-service.ts +36 -30
|
@@ -61,6 +61,8 @@ export interface BetterAuthConfig {
|
|
|
61
61
|
rateLimitRules?: Record<string, AuthRateLimitRule>;
|
|
62
62
|
/** Trusted origins for CORS. Falls back to UI_ORIGIN env var. */
|
|
63
63
|
trustedOrigins?: string[];
|
|
64
|
+
/** Brand name used in email templates. Default: "WOPR" */
|
|
65
|
+
brandName?: string;
|
|
64
66
|
/** Called after a new user signs up (e.g., create personal tenant). */
|
|
65
67
|
onUserCreated?: (userId: string, userName: string, email: string) => Promise<void>;
|
|
66
68
|
}
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -160,7 +160,7 @@ function authOptions(cfg) {
|
|
|
160
160
|
sendResetPassword: async ({ user, url }) => {
|
|
161
161
|
try {
|
|
162
162
|
const emailClient = getEmailClient();
|
|
163
|
-
const template = passwordResetEmailTemplate(url, user.email);
|
|
163
|
+
const template = passwordResetEmailTemplate(url, user.email, cfg.brandName);
|
|
164
164
|
await emailClient.send({
|
|
165
165
|
to: user.email,
|
|
166
166
|
...template,
|
|
@@ -199,7 +199,7 @@ function authOptions(cfg) {
|
|
|
199
199
|
const { token } = await generateVerificationToken(pool, user.id);
|
|
200
200
|
const verifyUrl = `${baseURL}${basePath}/verify?token=${token}`;
|
|
201
201
|
const emailClient = getEmailClient();
|
|
202
|
-
const template = verifyEmailTemplate(verifyUrl, user.email);
|
|
202
|
+
const template = verifyEmailTemplate(verifyUrl, user.email, cfg.brandName);
|
|
203
203
|
await emailClient.send({
|
|
204
204
|
to: user.email,
|
|
205
205
|
...template,
|
|
@@ -17,5 +17,16 @@ export declare class DrizzleNotificationTemplateRepository implements INotificat
|
|
|
17
17
|
htmlBody: string;
|
|
18
18
|
textBody: string;
|
|
19
19
|
}>): Promise<number>;
|
|
20
|
+
/**
|
|
21
|
+
* Re-seed all templates using upsert — overwrites existing content
|
|
22
|
+
* while preserving row IDs and createdAt timestamps.
|
|
23
|
+
*/
|
|
24
|
+
reseedAll(templates: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
subject: string;
|
|
28
|
+
htmlBody: string;
|
|
29
|
+
textBody: string;
|
|
30
|
+
}>): Promise<number>;
|
|
20
31
|
private toRow;
|
|
21
32
|
}
|
|
@@ -74,6 +74,24 @@ export class DrizzleNotificationTemplateRepository {
|
|
|
74
74
|
.returning({ id: notificationTemplates.id });
|
|
75
75
|
return result.length;
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Re-seed all templates using upsert — overwrites existing content
|
|
79
|
+
* while preserving row IDs and createdAt timestamps.
|
|
80
|
+
*/
|
|
81
|
+
async reseedAll(templates) {
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const t of templates) {
|
|
84
|
+
await this.upsert(t.name, {
|
|
85
|
+
description: t.description,
|
|
86
|
+
subject: t.subject,
|
|
87
|
+
htmlBody: t.htmlBody,
|
|
88
|
+
textBody: t.textBody,
|
|
89
|
+
active: true,
|
|
90
|
+
});
|
|
91
|
+
count++;
|
|
92
|
+
}
|
|
93
|
+
return count;
|
|
94
|
+
}
|
|
77
95
|
toRow(r) {
|
|
78
96
|
return {
|
|
79
97
|
id: r.id,
|
|
@@ -8,7 +8,10 @@ import type { INotificationQueueRepository } from "./notification-repository-typ
|
|
|
8
8
|
export declare class NotificationService {
|
|
9
9
|
private readonly queue;
|
|
10
10
|
private readonly appBaseUrl;
|
|
11
|
-
|
|
11
|
+
private readonly brandName;
|
|
12
|
+
constructor(queue: INotificationQueueRepository, appBaseUrl: string, brandName?: string);
|
|
13
|
+
/** Enqueue with brandName injected into every data bag. */
|
|
14
|
+
private enqueue;
|
|
12
15
|
private creditsUrl;
|
|
13
16
|
notifyLowBalance(tenantId: string, email: string, balanceDollars: string, estimatedDays: number): void;
|
|
14
17
|
notifyCreditsDepeleted(tenantId: string, email: string): void;
|
|
@@ -7,9 +7,15 @@
|
|
|
7
7
|
export class NotificationService {
|
|
8
8
|
queue;
|
|
9
9
|
appBaseUrl;
|
|
10
|
-
|
|
10
|
+
brandName;
|
|
11
|
+
constructor(queue, appBaseUrl, brandName = "WOPR") {
|
|
11
12
|
this.queue = queue;
|
|
12
13
|
this.appBaseUrl = appBaseUrl;
|
|
14
|
+
this.brandName = brandName;
|
|
15
|
+
}
|
|
16
|
+
/** Enqueue with brandName injected into every data bag. */
|
|
17
|
+
enqueue(tenantId, template, data) {
|
|
18
|
+
this.queue.enqueue(tenantId, template, { ...data, brandName: this.brandName });
|
|
13
19
|
}
|
|
14
20
|
creditsUrl() {
|
|
15
21
|
return `${this.appBaseUrl}/billing/credits`;
|
|
@@ -18,7 +24,7 @@ export class NotificationService {
|
|
|
18
24
|
// Credit & Billing
|
|
19
25
|
// ---------------------------------------------------------------------------
|
|
20
26
|
notifyLowBalance(tenantId, email, balanceDollars, estimatedDays) {
|
|
21
|
-
this.
|
|
27
|
+
this.enqueue(tenantId, "low-balance", {
|
|
22
28
|
email,
|
|
23
29
|
balanceDollars,
|
|
24
30
|
estimatedDaysRemaining: estimatedDays,
|
|
@@ -26,13 +32,13 @@ export class NotificationService {
|
|
|
26
32
|
});
|
|
27
33
|
}
|
|
28
34
|
notifyCreditsDepeleted(tenantId, email) {
|
|
29
|
-
this.
|
|
35
|
+
this.enqueue(tenantId, "credits-depleted", {
|
|
30
36
|
email,
|
|
31
37
|
creditsUrl: this.creditsUrl(),
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
40
|
notifyGracePeriodStart(tenantId, email, balanceDollars, graceDays) {
|
|
35
|
-
this.
|
|
41
|
+
this.enqueue(tenantId, "grace-period-start", {
|
|
36
42
|
email,
|
|
37
43
|
balanceDollars,
|
|
38
44
|
graceDays,
|
|
@@ -40,20 +46,20 @@ export class NotificationService {
|
|
|
40
46
|
});
|
|
41
47
|
}
|
|
42
48
|
notifyGracePeriodWarning(tenantId, email) {
|
|
43
|
-
this.
|
|
49
|
+
this.enqueue(tenantId, "grace-period-warning", {
|
|
44
50
|
email,
|
|
45
51
|
creditsUrl: this.creditsUrl(),
|
|
46
52
|
});
|
|
47
53
|
}
|
|
48
54
|
notifyAutoSuspended(tenantId, email, reason) {
|
|
49
|
-
this.
|
|
55
|
+
this.enqueue(tenantId, "auto-suspended", {
|
|
50
56
|
email,
|
|
51
57
|
reason,
|
|
52
58
|
creditsUrl: this.creditsUrl(),
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
notifyAutoTopUpSuccess(tenantId, email, amountDollars, newBalanceDollars) {
|
|
56
|
-
this.
|
|
62
|
+
this.enqueue(tenantId, "auto-topup-success", {
|
|
57
63
|
email,
|
|
58
64
|
amountDollars,
|
|
59
65
|
newBalanceDollars,
|
|
@@ -61,20 +67,20 @@ export class NotificationService {
|
|
|
61
67
|
});
|
|
62
68
|
}
|
|
63
69
|
notifyAutoTopUpFailed(tenantId, email) {
|
|
64
|
-
this.
|
|
70
|
+
this.enqueue(tenantId, "auto-topup-failed", {
|
|
65
71
|
email,
|
|
66
72
|
creditsUrl: this.creditsUrl(),
|
|
67
73
|
});
|
|
68
74
|
}
|
|
69
75
|
notifyAffiliateCreditMatch(tenantId, email, amountDollars) {
|
|
70
|
-
this.
|
|
76
|
+
this.enqueue(tenantId, "affiliate-credit-match", {
|
|
71
77
|
email,
|
|
72
78
|
amountDollars,
|
|
73
79
|
creditsUrl: this.creditsUrl(),
|
|
74
80
|
});
|
|
75
81
|
}
|
|
76
82
|
notifyDisputeCreated(tenantId, email, disputeId, amountDollars, reason) {
|
|
77
|
-
this.
|
|
83
|
+
this.enqueue(tenantId, "dispute-created", {
|
|
78
84
|
email,
|
|
79
85
|
disputeId,
|
|
80
86
|
amountDollars,
|
|
@@ -83,7 +89,7 @@ export class NotificationService {
|
|
|
83
89
|
});
|
|
84
90
|
}
|
|
85
91
|
notifyDisputeWon(tenantId, email, disputeId, amountDollars) {
|
|
86
|
-
this.
|
|
92
|
+
this.enqueue(tenantId, "dispute-won", {
|
|
87
93
|
email,
|
|
88
94
|
disputeId,
|
|
89
95
|
amountDollars,
|
|
@@ -91,7 +97,7 @@ export class NotificationService {
|
|
|
91
97
|
});
|
|
92
98
|
}
|
|
93
99
|
notifyDisputeLost(tenantId, email, disputeId, amountDollars) {
|
|
94
|
-
this.
|
|
100
|
+
this.enqueue(tenantId, "dispute-lost", {
|
|
95
101
|
email,
|
|
96
102
|
disputeId,
|
|
97
103
|
amountDollars,
|
|
@@ -99,7 +105,7 @@ export class NotificationService {
|
|
|
99
105
|
});
|
|
100
106
|
}
|
|
101
107
|
notifySpendThresholdAlert(tenantId, email, currentSpendDollars, alertAtDollars) {
|
|
102
|
-
this.
|
|
108
|
+
this.enqueue(tenantId, "spend-alert", {
|
|
103
109
|
email,
|
|
104
110
|
currentSpendDollars,
|
|
105
111
|
alertAtDollars,
|
|
@@ -107,7 +113,7 @@ export class NotificationService {
|
|
|
107
113
|
});
|
|
108
114
|
}
|
|
109
115
|
notifyCreditPurchaseReceipt(tenantId, email, amountDollars, newBalanceDollars) {
|
|
110
|
-
this.
|
|
116
|
+
this.enqueue(tenantId, "credit-purchase-receipt", {
|
|
111
117
|
email,
|
|
112
118
|
amountDollars,
|
|
113
119
|
newBalanceDollars,
|
|
@@ -116,7 +122,7 @@ export class NotificationService {
|
|
|
116
122
|
}
|
|
117
123
|
notifyDividendWeeklyDigest(tenantId, email, weeklyTotalDollars, weeklyTotalCents, lifetimeTotalDollars, distributionCount, poolAvgCents, activeUsersAvg, nextDividendDate, weekStartDate, weekEndDate) {
|
|
118
124
|
const unsubscribeUrl = `${this.appBaseUrl}/settings/notifications`;
|
|
119
|
-
this.
|
|
125
|
+
this.enqueue(tenantId, "dividend-weekly-digest", {
|
|
120
126
|
email,
|
|
121
127
|
weeklyTotalDollars,
|
|
122
128
|
weeklyTotalCents,
|
|
@@ -132,7 +138,7 @@ export class NotificationService {
|
|
|
132
138
|
});
|
|
133
139
|
}
|
|
134
140
|
notifyCryptoPaymentConfirmed(tenantId, email, amountDollars, newBalanceDollars) {
|
|
135
|
-
this.
|
|
141
|
+
this.enqueue(tenantId, "crypto-payment-confirmed", {
|
|
136
142
|
email,
|
|
137
143
|
amountDollars,
|
|
138
144
|
newBalanceDollars,
|
|
@@ -142,56 +148,56 @@ export class NotificationService {
|
|
|
142
148
|
// Account
|
|
143
149
|
// ---------------------------------------------------------------------------
|
|
144
150
|
notifyAdminSuspended(tenantId, email, reason) {
|
|
145
|
-
this.
|
|
151
|
+
this.enqueue(tenantId, "admin-suspended", { email, reason });
|
|
146
152
|
}
|
|
147
153
|
notifyAdminReactivated(tenantId, email) {
|
|
148
|
-
this.
|
|
154
|
+
this.enqueue(tenantId, "admin-reactivated", { email });
|
|
149
155
|
}
|
|
150
156
|
notifyCreditsGranted(tenantId, email, amountDollars, reason) {
|
|
151
|
-
this.
|
|
157
|
+
this.enqueue(tenantId, "credits-granted", { email, amountDollars, reason });
|
|
152
158
|
}
|
|
153
159
|
notifyRoleChanged(tenantId, email, newRole) {
|
|
154
|
-
this.
|
|
160
|
+
this.enqueue(tenantId, "role-changed", { email, newRole });
|
|
155
161
|
}
|
|
156
162
|
notifyTeamInvite(tenantId, email, tenantName, inviteUrl) {
|
|
157
|
-
this.
|
|
163
|
+
this.enqueue(tenantId, "team-invite", { email, tenantName, inviteUrl });
|
|
158
164
|
}
|
|
159
165
|
// ---------------------------------------------------------------------------
|
|
160
166
|
// Agent & Channel
|
|
161
167
|
// ---------------------------------------------------------------------------
|
|
162
168
|
notifyAgentCreated(tenantId, email, agentName) {
|
|
163
|
-
this.
|
|
169
|
+
this.enqueue(tenantId, "agent-created", { email, agentName });
|
|
164
170
|
}
|
|
165
171
|
notifyChannelConnected(tenantId, email, channelName, agentName) {
|
|
166
|
-
this.
|
|
172
|
+
this.enqueue(tenantId, "channel-connected", { email, channelName, agentName });
|
|
167
173
|
}
|
|
168
174
|
notifyChannelDisconnected(tenantId, email, channelName, agentName, reason) {
|
|
169
|
-
this.
|
|
175
|
+
this.enqueue(tenantId, "channel-disconnected", { email, channelName, agentName, reason });
|
|
170
176
|
}
|
|
171
177
|
notifyAgentSuspended(tenantId, email, agentName, reason) {
|
|
172
|
-
this.
|
|
178
|
+
this.enqueue(tenantId, "agent-suspended", { email, agentName, reason });
|
|
173
179
|
}
|
|
174
180
|
// ---------------------------------------------------------------------------
|
|
175
181
|
// Account Deletion
|
|
176
182
|
// ---------------------------------------------------------------------------
|
|
177
183
|
notifyAccountDeletionRequested(tenantId, email, deleteAfterDate) {
|
|
178
|
-
this.
|
|
184
|
+
this.enqueue(tenantId, "account-deletion-requested", {
|
|
179
185
|
email,
|
|
180
186
|
deleteAfterDate,
|
|
181
187
|
cancelUrl: `${this.appBaseUrl}/settings/account`,
|
|
182
188
|
});
|
|
183
189
|
}
|
|
184
190
|
notifyAccountDeletionCancelled(tenantId, email) {
|
|
185
|
-
this.
|
|
191
|
+
this.enqueue(tenantId, "account-deletion-cancelled", { email });
|
|
186
192
|
}
|
|
187
193
|
notifyAccountDeletionCompleted(tenantId, email) {
|
|
188
|
-
this.
|
|
194
|
+
this.enqueue(tenantId, "account-deletion-completed", { email });
|
|
189
195
|
}
|
|
190
196
|
// ---------------------------------------------------------------------------
|
|
191
197
|
// Fleet Updates
|
|
192
198
|
// ---------------------------------------------------------------------------
|
|
193
199
|
notifyFleetUpdateAvailable(tenantId, email, version, changelogDate, changelogSummary) {
|
|
194
|
-
this.
|
|
200
|
+
this.enqueue(tenantId, "fleet-update-available", {
|
|
195
201
|
email,
|
|
196
202
|
version,
|
|
197
203
|
changelogDate,
|
|
@@ -200,7 +206,7 @@ export class NotificationService {
|
|
|
200
206
|
});
|
|
201
207
|
}
|
|
202
208
|
notifyFleetUpdateComplete(tenantId, email, version, succeeded, failed) {
|
|
203
|
-
this.
|
|
209
|
+
this.enqueue(tenantId, "fleet-update-complete", {
|
|
204
210
|
email,
|
|
205
211
|
version,
|
|
206
212
|
succeeded,
|
|
@@ -213,6 +219,6 @@ export class NotificationService {
|
|
|
213
219
|
// Admin custom email
|
|
214
220
|
// ---------------------------------------------------------------------------
|
|
215
221
|
sendCustomEmail(tenantId, email, subject, bodyText) {
|
|
216
|
-
this.
|
|
222
|
+
this.enqueue(tenantId, "custom", { email, subject, bodyText });
|
|
217
223
|
}
|
|
218
224
|
}
|
|
@@ -25,6 +25,7 @@ describe("NotificationService", () => {
|
|
|
25
25
|
balanceDollars: "$2.50",
|
|
26
26
|
estimatedDaysRemaining: 5,
|
|
27
27
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
28
|
+
brandName: "WOPR",
|
|
28
29
|
});
|
|
29
30
|
});
|
|
30
31
|
});
|
|
@@ -34,6 +35,7 @@ describe("NotificationService", () => {
|
|
|
34
35
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "credits-depleted", {
|
|
35
36
|
email: "user@example.com",
|
|
36
37
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
38
|
+
brandName: "WOPR",
|
|
37
39
|
});
|
|
38
40
|
});
|
|
39
41
|
});
|
|
@@ -45,6 +47,7 @@ describe("NotificationService", () => {
|
|
|
45
47
|
balanceDollars: "$0.50",
|
|
46
48
|
graceDays: 7,
|
|
47
49
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
50
|
+
brandName: "WOPR",
|
|
48
51
|
});
|
|
49
52
|
});
|
|
50
53
|
});
|
|
@@ -54,6 +57,7 @@ describe("NotificationService", () => {
|
|
|
54
57
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "grace-period-warning", {
|
|
55
58
|
email: "user@example.com",
|
|
56
59
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
60
|
+
brandName: "WOPR",
|
|
57
61
|
});
|
|
58
62
|
});
|
|
59
63
|
});
|
|
@@ -64,6 +68,7 @@ describe("NotificationService", () => {
|
|
|
64
68
|
email: "user@example.com",
|
|
65
69
|
reason: "Grace period expired",
|
|
66
70
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
71
|
+
brandName: "WOPR",
|
|
67
72
|
});
|
|
68
73
|
});
|
|
69
74
|
});
|
|
@@ -73,6 +78,7 @@ describe("NotificationService", () => {
|
|
|
73
78
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "admin-suspended", {
|
|
74
79
|
email: "user@example.com",
|
|
75
80
|
reason: "ToS violation",
|
|
81
|
+
brandName: "WOPR",
|
|
76
82
|
});
|
|
77
83
|
});
|
|
78
84
|
});
|
|
@@ -81,6 +87,7 @@ describe("NotificationService", () => {
|
|
|
81
87
|
service.notifyAdminReactivated("tenant-1", "user@example.com");
|
|
82
88
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "admin-reactivated", {
|
|
83
89
|
email: "user@example.com",
|
|
90
|
+
brandName: "WOPR",
|
|
84
91
|
});
|
|
85
92
|
});
|
|
86
93
|
});
|
|
@@ -91,6 +98,7 @@ describe("NotificationService", () => {
|
|
|
91
98
|
email: "user@example.com",
|
|
92
99
|
amountDollars: "$5.00",
|
|
93
100
|
reason: "Support credit",
|
|
101
|
+
brandName: "WOPR",
|
|
94
102
|
});
|
|
95
103
|
});
|
|
96
104
|
});
|
|
@@ -100,6 +108,7 @@ describe("NotificationService", () => {
|
|
|
100
108
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "role-changed", {
|
|
101
109
|
email: "user@example.com",
|
|
102
110
|
newRole: "tenant_admin",
|
|
111
|
+
brandName: "WOPR",
|
|
103
112
|
});
|
|
104
113
|
});
|
|
105
114
|
});
|
|
@@ -110,6 +119,7 @@ describe("NotificationService", () => {
|
|
|
110
119
|
email: "user@example.com",
|
|
111
120
|
tenantName: "Acme Corp",
|
|
112
121
|
inviteUrl: "https://app.wopr.bot/invite/abc",
|
|
122
|
+
brandName: "WOPR",
|
|
113
123
|
});
|
|
114
124
|
});
|
|
115
125
|
});
|
|
@@ -121,6 +131,7 @@ describe("NotificationService", () => {
|
|
|
121
131
|
channelName: "Discord",
|
|
122
132
|
agentName: "MyBot",
|
|
123
133
|
reason: "Token expired",
|
|
134
|
+
brandName: "WOPR",
|
|
124
135
|
});
|
|
125
136
|
});
|
|
126
137
|
});
|
|
@@ -131,6 +142,7 @@ describe("NotificationService", () => {
|
|
|
131
142
|
email: "user@example.com",
|
|
132
143
|
subject: "Hello there",
|
|
133
144
|
bodyText: "This is the body.",
|
|
145
|
+
brandName: "WOPR",
|
|
134
146
|
});
|
|
135
147
|
});
|
|
136
148
|
});
|
|
@@ -143,6 +155,7 @@ describe("NotificationService", () => {
|
|
|
143
155
|
amountDollars: "$50.00",
|
|
144
156
|
reason: "fraudulent",
|
|
145
157
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
158
|
+
brandName: "WOPR",
|
|
146
159
|
});
|
|
147
160
|
});
|
|
148
161
|
});
|
|
@@ -154,6 +167,7 @@ describe("NotificationService", () => {
|
|
|
154
167
|
disputeId: "dp_123",
|
|
155
168
|
amountDollars: "$50.00",
|
|
156
169
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
170
|
+
brandName: "WOPR",
|
|
157
171
|
});
|
|
158
172
|
});
|
|
159
173
|
});
|
package/package.json
CHANGED
package/src/auth/better-auth.ts
CHANGED
|
@@ -80,6 +80,10 @@ export interface BetterAuthConfig {
|
|
|
80
80
|
/** Trusted origins for CORS. Falls back to UI_ORIGIN env var. */
|
|
81
81
|
trustedOrigins?: string[];
|
|
82
82
|
|
|
83
|
+
// --- Branding ---
|
|
84
|
+
/** Brand name used in email templates. Default: "WOPR" */
|
|
85
|
+
brandName?: string;
|
|
86
|
+
|
|
83
87
|
// --- Lifecycle hooks ---
|
|
84
88
|
/** Called after a new user signs up (e.g., create personal tenant). */
|
|
85
89
|
onUserCreated?: (userId: string, userName: string, email: string) => Promise<void>;
|
|
@@ -228,7 +232,7 @@ function authOptions(cfg: BetterAuthConfig): BetterAuthOptions {
|
|
|
228
232
|
sendResetPassword: async ({ user, url }) => {
|
|
229
233
|
try {
|
|
230
234
|
const emailClient = getEmailClient();
|
|
231
|
-
const template = passwordResetEmailTemplate(url, user.email);
|
|
235
|
+
const template = passwordResetEmailTemplate(url, user.email, cfg.brandName);
|
|
232
236
|
await emailClient.send({
|
|
233
237
|
to: user.email,
|
|
234
238
|
...template,
|
|
@@ -266,7 +270,7 @@ function authOptions(cfg: BetterAuthConfig): BetterAuthOptions {
|
|
|
266
270
|
const { token } = await generateVerificationToken(pool, user.id);
|
|
267
271
|
const verifyUrl = `${baseURL}${basePath}/verify?token=${token}`;
|
|
268
272
|
const emailClient = getEmailClient();
|
|
269
|
-
const template = verifyEmailTemplate(verifyUrl, user.email);
|
|
273
|
+
const template = verifyEmailTemplate(verifyUrl, user.email, cfg.brandName);
|
|
270
274
|
await emailClient.send({
|
|
271
275
|
to: user.email,
|
|
272
276
|
...template,
|
|
@@ -92,6 +92,33 @@ export class DrizzleNotificationTemplateRepository implements INotificationTempl
|
|
|
92
92
|
return result.length;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Re-seed all templates using upsert — overwrites existing content
|
|
97
|
+
* while preserving row IDs and createdAt timestamps.
|
|
98
|
+
*/
|
|
99
|
+
async reseedAll(
|
|
100
|
+
templates: Array<{
|
|
101
|
+
name: string;
|
|
102
|
+
description: string;
|
|
103
|
+
subject: string;
|
|
104
|
+
htmlBody: string;
|
|
105
|
+
textBody: string;
|
|
106
|
+
}>,
|
|
107
|
+
): Promise<number> {
|
|
108
|
+
let count = 0;
|
|
109
|
+
for (const t of templates) {
|
|
110
|
+
await this.upsert(t.name, {
|
|
111
|
+
description: t.description,
|
|
112
|
+
subject: t.subject,
|
|
113
|
+
htmlBody: t.htmlBody,
|
|
114
|
+
textBody: t.textBody,
|
|
115
|
+
active: true,
|
|
116
|
+
});
|
|
117
|
+
count++;
|
|
118
|
+
}
|
|
119
|
+
return count;
|
|
120
|
+
}
|
|
121
|
+
|
|
95
122
|
private toRow(r: typeof notificationTemplates.$inferSelect): NotificationTemplateRow {
|
|
96
123
|
return {
|
|
97
124
|
id: r.id,
|
|
@@ -30,6 +30,7 @@ describe("NotificationService", () => {
|
|
|
30
30
|
balanceDollars: "$2.50",
|
|
31
31
|
estimatedDaysRemaining: 5,
|
|
32
32
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
33
|
+
brandName: "WOPR",
|
|
33
34
|
});
|
|
34
35
|
});
|
|
35
36
|
});
|
|
@@ -40,6 +41,7 @@ describe("NotificationService", () => {
|
|
|
40
41
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "credits-depleted", {
|
|
41
42
|
email: "user@example.com",
|
|
42
43
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
44
|
+
brandName: "WOPR",
|
|
43
45
|
});
|
|
44
46
|
});
|
|
45
47
|
});
|
|
@@ -52,6 +54,7 @@ describe("NotificationService", () => {
|
|
|
52
54
|
balanceDollars: "$0.50",
|
|
53
55
|
graceDays: 7,
|
|
54
56
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
57
|
+
brandName: "WOPR",
|
|
55
58
|
});
|
|
56
59
|
});
|
|
57
60
|
});
|
|
@@ -62,6 +65,7 @@ describe("NotificationService", () => {
|
|
|
62
65
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "grace-period-warning", {
|
|
63
66
|
email: "user@example.com",
|
|
64
67
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
68
|
+
brandName: "WOPR",
|
|
65
69
|
});
|
|
66
70
|
});
|
|
67
71
|
});
|
|
@@ -73,6 +77,7 @@ describe("NotificationService", () => {
|
|
|
73
77
|
email: "user@example.com",
|
|
74
78
|
reason: "Grace period expired",
|
|
75
79
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
80
|
+
brandName: "WOPR",
|
|
76
81
|
});
|
|
77
82
|
});
|
|
78
83
|
});
|
|
@@ -83,6 +88,7 @@ describe("NotificationService", () => {
|
|
|
83
88
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "admin-suspended", {
|
|
84
89
|
email: "user@example.com",
|
|
85
90
|
reason: "ToS violation",
|
|
91
|
+
brandName: "WOPR",
|
|
86
92
|
});
|
|
87
93
|
});
|
|
88
94
|
});
|
|
@@ -92,6 +98,7 @@ describe("NotificationService", () => {
|
|
|
92
98
|
service.notifyAdminReactivated("tenant-1", "user@example.com");
|
|
93
99
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "admin-reactivated", {
|
|
94
100
|
email: "user@example.com",
|
|
101
|
+
brandName: "WOPR",
|
|
95
102
|
});
|
|
96
103
|
});
|
|
97
104
|
});
|
|
@@ -103,6 +110,7 @@ describe("NotificationService", () => {
|
|
|
103
110
|
email: "user@example.com",
|
|
104
111
|
amountDollars: "$5.00",
|
|
105
112
|
reason: "Support credit",
|
|
113
|
+
brandName: "WOPR",
|
|
106
114
|
});
|
|
107
115
|
});
|
|
108
116
|
});
|
|
@@ -113,6 +121,7 @@ describe("NotificationService", () => {
|
|
|
113
121
|
expect(queue.enqueue).toHaveBeenCalledWith("tenant-1", "role-changed", {
|
|
114
122
|
email: "user@example.com",
|
|
115
123
|
newRole: "tenant_admin",
|
|
124
|
+
brandName: "WOPR",
|
|
116
125
|
});
|
|
117
126
|
});
|
|
118
127
|
});
|
|
@@ -124,6 +133,7 @@ describe("NotificationService", () => {
|
|
|
124
133
|
email: "user@example.com",
|
|
125
134
|
tenantName: "Acme Corp",
|
|
126
135
|
inviteUrl: "https://app.wopr.bot/invite/abc",
|
|
136
|
+
brandName: "WOPR",
|
|
127
137
|
});
|
|
128
138
|
});
|
|
129
139
|
});
|
|
@@ -136,6 +146,7 @@ describe("NotificationService", () => {
|
|
|
136
146
|
channelName: "Discord",
|
|
137
147
|
agentName: "MyBot",
|
|
138
148
|
reason: "Token expired",
|
|
149
|
+
brandName: "WOPR",
|
|
139
150
|
});
|
|
140
151
|
});
|
|
141
152
|
});
|
|
@@ -147,6 +158,7 @@ describe("NotificationService", () => {
|
|
|
147
158
|
email: "user@example.com",
|
|
148
159
|
subject: "Hello there",
|
|
149
160
|
bodyText: "This is the body.",
|
|
161
|
+
brandName: "WOPR",
|
|
150
162
|
});
|
|
151
163
|
});
|
|
152
164
|
});
|
|
@@ -160,6 +172,7 @@ describe("NotificationService", () => {
|
|
|
160
172
|
amountDollars: "$50.00",
|
|
161
173
|
reason: "fraudulent",
|
|
162
174
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
175
|
+
brandName: "WOPR",
|
|
163
176
|
});
|
|
164
177
|
});
|
|
165
178
|
});
|
|
@@ -172,6 +185,7 @@ describe("NotificationService", () => {
|
|
|
172
185
|
disputeId: "dp_123",
|
|
173
186
|
amountDollars: "$50.00",
|
|
174
187
|
creditsUrl: `${BASE_URL}/billing/credits`,
|
|
188
|
+
brandName: "WOPR",
|
|
175
189
|
});
|
|
176
190
|
});
|
|
177
191
|
});
|
|
@@ -11,8 +11,14 @@ export class NotificationService {
|
|
|
11
11
|
constructor(
|
|
12
12
|
private readonly queue: INotificationQueueRepository,
|
|
13
13
|
private readonly appBaseUrl: string,
|
|
14
|
+
private readonly brandName: string = "WOPR",
|
|
14
15
|
) {}
|
|
15
16
|
|
|
17
|
+
/** Enqueue with brandName injected into every data bag. */
|
|
18
|
+
private enqueue(tenantId: string, template: string, data: Record<string, unknown>): void {
|
|
19
|
+
this.queue.enqueue(tenantId, template, { ...data, brandName: this.brandName });
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
private creditsUrl(): string {
|
|
17
23
|
return `${this.appBaseUrl}/billing/credits`;
|
|
18
24
|
}
|
|
@@ -22,7 +28,7 @@ export class NotificationService {
|
|
|
22
28
|
// ---------------------------------------------------------------------------
|
|
23
29
|
|
|
24
30
|
notifyLowBalance(tenantId: string, email: string, balanceDollars: string, estimatedDays: number): void {
|
|
25
|
-
this.
|
|
31
|
+
this.enqueue(tenantId, "low-balance", {
|
|
26
32
|
email,
|
|
27
33
|
balanceDollars,
|
|
28
34
|
estimatedDaysRemaining: estimatedDays,
|
|
@@ -31,14 +37,14 @@ export class NotificationService {
|
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
notifyCreditsDepeleted(tenantId: string, email: string): void {
|
|
34
|
-
this.
|
|
40
|
+
this.enqueue(tenantId, "credits-depleted", {
|
|
35
41
|
email,
|
|
36
42
|
creditsUrl: this.creditsUrl(),
|
|
37
43
|
});
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
notifyGracePeriodStart(tenantId: string, email: string, balanceDollars: string, graceDays: number): void {
|
|
41
|
-
this.
|
|
47
|
+
this.enqueue(tenantId, "grace-period-start", {
|
|
42
48
|
email,
|
|
43
49
|
balanceDollars,
|
|
44
50
|
graceDays,
|
|
@@ -47,14 +53,14 @@ export class NotificationService {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
notifyGracePeriodWarning(tenantId: string, email: string): void {
|
|
50
|
-
this.
|
|
56
|
+
this.enqueue(tenantId, "grace-period-warning", {
|
|
51
57
|
email,
|
|
52
58
|
creditsUrl: this.creditsUrl(),
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
notifyAutoSuspended(tenantId: string, email: string, reason: string): void {
|
|
57
|
-
this.
|
|
63
|
+
this.enqueue(tenantId, "auto-suspended", {
|
|
58
64
|
email,
|
|
59
65
|
reason,
|
|
60
66
|
creditsUrl: this.creditsUrl(),
|
|
@@ -62,7 +68,7 @@ export class NotificationService {
|
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
notifyAutoTopUpSuccess(tenantId: string, email: string, amountDollars: string, newBalanceDollars: string): void {
|
|
65
|
-
this.
|
|
71
|
+
this.enqueue(tenantId, "auto-topup-success", {
|
|
66
72
|
email,
|
|
67
73
|
amountDollars,
|
|
68
74
|
newBalanceDollars,
|
|
@@ -71,14 +77,14 @@ export class NotificationService {
|
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
notifyAutoTopUpFailed(tenantId: string, email: string): void {
|
|
74
|
-
this.
|
|
80
|
+
this.enqueue(tenantId, "auto-topup-failed", {
|
|
75
81
|
email,
|
|
76
82
|
creditsUrl: this.creditsUrl(),
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
notifyAffiliateCreditMatch(tenantId: string, email: string, amountDollars: string): void {
|
|
81
|
-
this.
|
|
87
|
+
this.enqueue(tenantId, "affiliate-credit-match", {
|
|
82
88
|
email,
|
|
83
89
|
amountDollars,
|
|
84
90
|
creditsUrl: this.creditsUrl(),
|
|
@@ -92,7 +98,7 @@ export class NotificationService {
|
|
|
92
98
|
amountDollars: string,
|
|
93
99
|
reason: string,
|
|
94
100
|
): void {
|
|
95
|
-
this.
|
|
101
|
+
this.enqueue(tenantId, "dispute-created", {
|
|
96
102
|
email,
|
|
97
103
|
disputeId,
|
|
98
104
|
amountDollars,
|
|
@@ -102,7 +108,7 @@ export class NotificationService {
|
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
notifyDisputeWon(tenantId: string, email: string, disputeId: string, amountDollars: string): void {
|
|
105
|
-
this.
|
|
111
|
+
this.enqueue(tenantId, "dispute-won", {
|
|
106
112
|
email,
|
|
107
113
|
disputeId,
|
|
108
114
|
amountDollars,
|
|
@@ -111,7 +117,7 @@ export class NotificationService {
|
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
notifyDisputeLost(tenantId: string, email: string, disputeId: string, amountDollars: string): void {
|
|
114
|
-
this.
|
|
120
|
+
this.enqueue(tenantId, "dispute-lost", {
|
|
115
121
|
email,
|
|
116
122
|
disputeId,
|
|
117
123
|
amountDollars,
|
|
@@ -125,7 +131,7 @@ export class NotificationService {
|
|
|
125
131
|
currentSpendDollars: string,
|
|
126
132
|
alertAtDollars: string,
|
|
127
133
|
): void {
|
|
128
|
-
this.
|
|
134
|
+
this.enqueue(tenantId, "spend-alert", {
|
|
129
135
|
email,
|
|
130
136
|
currentSpendDollars,
|
|
131
137
|
alertAtDollars,
|
|
@@ -134,7 +140,7 @@ export class NotificationService {
|
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
notifyCreditPurchaseReceipt(tenantId: string, email: string, amountDollars: string, newBalanceDollars: string): void {
|
|
137
|
-
this.
|
|
143
|
+
this.enqueue(tenantId, "credit-purchase-receipt", {
|
|
138
144
|
email,
|
|
139
145
|
amountDollars,
|
|
140
146
|
newBalanceDollars,
|
|
@@ -156,7 +162,7 @@ export class NotificationService {
|
|
|
156
162
|
weekEndDate: string,
|
|
157
163
|
): void {
|
|
158
164
|
const unsubscribeUrl = `${this.appBaseUrl}/settings/notifications`;
|
|
159
|
-
this.
|
|
165
|
+
this.enqueue(tenantId, "dividend-weekly-digest", {
|
|
160
166
|
email,
|
|
161
167
|
weeklyTotalDollars,
|
|
162
168
|
weeklyTotalCents,
|
|
@@ -178,7 +184,7 @@ export class NotificationService {
|
|
|
178
184
|
amountDollars: string,
|
|
179
185
|
newBalanceDollars: string,
|
|
180
186
|
): void {
|
|
181
|
-
this.
|
|
187
|
+
this.enqueue(tenantId, "crypto-payment-confirmed", {
|
|
182
188
|
email,
|
|
183
189
|
amountDollars,
|
|
184
190
|
newBalanceDollars,
|
|
@@ -190,23 +196,23 @@ export class NotificationService {
|
|
|
190
196
|
// ---------------------------------------------------------------------------
|
|
191
197
|
|
|
192
198
|
notifyAdminSuspended(tenantId: string, email: string, reason: string): void {
|
|
193
|
-
this.
|
|
199
|
+
this.enqueue(tenantId, "admin-suspended", { email, reason });
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
notifyAdminReactivated(tenantId: string, email: string): void {
|
|
197
|
-
this.
|
|
203
|
+
this.enqueue(tenantId, "admin-reactivated", { email });
|
|
198
204
|
}
|
|
199
205
|
|
|
200
206
|
notifyCreditsGranted(tenantId: string, email: string, amountDollars: string, reason: string): void {
|
|
201
|
-
this.
|
|
207
|
+
this.enqueue(tenantId, "credits-granted", { email, amountDollars, reason });
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
notifyRoleChanged(tenantId: string, email: string, newRole: string): void {
|
|
205
|
-
this.
|
|
211
|
+
this.enqueue(tenantId, "role-changed", { email, newRole });
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
notifyTeamInvite(tenantId: string, email: string, tenantName: string, inviteUrl: string): void {
|
|
209
|
-
this.
|
|
215
|
+
this.enqueue(tenantId, "team-invite", { email, tenantName, inviteUrl });
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
// ---------------------------------------------------------------------------
|
|
@@ -214,11 +220,11 @@ export class NotificationService {
|
|
|
214
220
|
// ---------------------------------------------------------------------------
|
|
215
221
|
|
|
216
222
|
notifyAgentCreated(tenantId: string, email: string, agentName: string): void {
|
|
217
|
-
this.
|
|
223
|
+
this.enqueue(tenantId, "agent-created", { email, agentName });
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
notifyChannelConnected(tenantId: string, email: string, channelName: string, agentName: string): void {
|
|
221
|
-
this.
|
|
227
|
+
this.enqueue(tenantId, "channel-connected", { email, channelName, agentName });
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
notifyChannelDisconnected(
|
|
@@ -228,11 +234,11 @@ export class NotificationService {
|
|
|
228
234
|
agentName: string,
|
|
229
235
|
reason: string,
|
|
230
236
|
): void {
|
|
231
|
-
this.
|
|
237
|
+
this.enqueue(tenantId, "channel-disconnected", { email, channelName, agentName, reason });
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
notifyAgentSuspended(tenantId: string, email: string, agentName: string, reason: string): void {
|
|
235
|
-
this.
|
|
241
|
+
this.enqueue(tenantId, "agent-suspended", { email, agentName, reason });
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
// ---------------------------------------------------------------------------
|
|
@@ -240,7 +246,7 @@ export class NotificationService {
|
|
|
240
246
|
// ---------------------------------------------------------------------------
|
|
241
247
|
|
|
242
248
|
notifyAccountDeletionRequested(tenantId: string, email: string, deleteAfterDate: string): void {
|
|
243
|
-
this.
|
|
249
|
+
this.enqueue(tenantId, "account-deletion-requested", {
|
|
244
250
|
email,
|
|
245
251
|
deleteAfterDate,
|
|
246
252
|
cancelUrl: `${this.appBaseUrl}/settings/account`,
|
|
@@ -248,11 +254,11 @@ export class NotificationService {
|
|
|
248
254
|
}
|
|
249
255
|
|
|
250
256
|
notifyAccountDeletionCancelled(tenantId: string, email: string): void {
|
|
251
|
-
this.
|
|
257
|
+
this.enqueue(tenantId, "account-deletion-cancelled", { email });
|
|
252
258
|
}
|
|
253
259
|
|
|
254
260
|
notifyAccountDeletionCompleted(tenantId: string, email: string): void {
|
|
255
|
-
this.
|
|
261
|
+
this.enqueue(tenantId, "account-deletion-completed", { email });
|
|
256
262
|
}
|
|
257
263
|
|
|
258
264
|
// ---------------------------------------------------------------------------
|
|
@@ -266,7 +272,7 @@ export class NotificationService {
|
|
|
266
272
|
changelogDate: string,
|
|
267
273
|
changelogSummary: string,
|
|
268
274
|
): void {
|
|
269
|
-
this.
|
|
275
|
+
this.enqueue(tenantId, "fleet-update-available", {
|
|
270
276
|
email,
|
|
271
277
|
version,
|
|
272
278
|
changelogDate,
|
|
@@ -276,7 +282,7 @@ export class NotificationService {
|
|
|
276
282
|
}
|
|
277
283
|
|
|
278
284
|
notifyFleetUpdateComplete(tenantId: string, email: string, version: string, succeeded: number, failed: number): void {
|
|
279
|
-
this.
|
|
285
|
+
this.enqueue(tenantId, "fleet-update-complete", {
|
|
280
286
|
email,
|
|
281
287
|
version,
|
|
282
288
|
succeeded,
|
|
@@ -291,6 +297,6 @@ export class NotificationService {
|
|
|
291
297
|
// ---------------------------------------------------------------------------
|
|
292
298
|
|
|
293
299
|
sendCustomEmail(tenantId: string, email: string, subject: string, bodyText: string): void {
|
|
294
|
-
this.
|
|
300
|
+
this.enqueue(tenantId, "custom", { email, subject, bodyText });
|
|
295
301
|
}
|
|
296
302
|
}
|