Skip to content

Commit aff5ea6

Browse files
dahliaclaude
andcommitted
Improve invitation system error handling and performance
- Add invitation quota rollback when email sending fails - Implement INVITER_EMAIL_SEND_FAILED error type for proper user feedback - Add email template caching to reduce disk I/O overhead - Add error handling for invalid locale tags in availableLocales query Co-Authored-By: Claude <[email protected]>
1 parent bd31b49 commit aff5ea6

File tree

2 files changed

+33
-16
lines changed

2 files changed

+33
-16
lines changed

graphql/invite.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ InvitationRef.implement({
6969
});
7070

7171
const InviteInviterError = builder.enumType("InviteInviterError", {
72-
values: ["INVITER_NOT_AUTHENTICATED", "INVITER_NO_INVITATIONS_LEFT", "INVITER_EMAIL_SEND_FAILED"] as const,
72+
values: [
73+
"INVITER_NOT_AUTHENTICATED",
74+
"INVITER_NO_INVITATIONS_LEFT",
75+
"INVITER_EMAIL_SEND_FAILED",
76+
] as const,
7377
});
7478

7579
const InviteEmailError = builder.enumType("InviteEmailError", {
@@ -227,7 +231,7 @@ builder.mutationField("invite", (t) =>
227231
await ctx.db.update(accountTable).set({
228232
leftInvitations: sql`${accountTable.leftInvitations} + 1`,
229233
}).where(eq(accountTable.id, ctx.account.id));
230-
234+
231235
// Return validation error to inform the user
232236
return {
233237
inviter: "INVITER_EMAIL_SEND_FAILED",
@@ -245,26 +249,34 @@ builder.mutationField("invite", (t) =>
245249
const LOCALES_DIR = join(import.meta.dirname!, "locales");
246250

247251
// Cache for email templates
248-
let cachedTemplates: Map<string, { subject: string; emailContent: string; emailContentWithMessage: string }> | null = null;
252+
let cachedTemplates:
253+
| Map<
254+
string,
255+
{ subject: string; emailContent: string; emailContentWithMessage: string }
256+
>
257+
| null = null;
249258
let cachedAvailableLocales: Record<string, string> | null = null;
250259

251260
async function loadEmailTemplates(): Promise<void> {
252261
if (cachedTemplates && cachedAvailableLocales) return;
253-
262+
254263
const availableLocales: Record<string, string> = {};
255-
const templates = new Map<string, { subject: string; emailContent: string; emailContentWithMessage: string }>();
256-
264+
const templates = new Map<
265+
string,
266+
{ subject: string; emailContent: string; emailContentWithMessage: string }
267+
>();
268+
257269
const files = expandGlob(join(LOCALES_DIR, "*.json"), {
258270
includeDirs: false,
259271
});
260-
272+
261273
for await (const file of files) {
262274
if (!file.isFile) continue;
263275
const match = file.name.match(/^(.+)\.json$/);
264276
if (match == null) continue;
265277
const localeName = match[1];
266278
availableLocales[localeName] = file.path;
267-
279+
268280
try {
269281
const json = await Deno.readTextFile(file.path);
270282
const data = JSON.parse(json);
@@ -274,10 +286,13 @@ async function loadEmailTemplates(): Promise<void> {
274286
emailContentWithMessage: data.invite.emailContentWithMessage,
275287
});
276288
} catch (error) {
277-
console.warn(`Failed to load email template for locale ${localeName}:`, error);
289+
console.warn(
290+
`Failed to load email template for locale ${localeName}:`,
291+
error,
292+
);
278293
}
279294
}
280-
295+
281296
cachedTemplates = templates;
282297
cachedAvailableLocales = availableLocales;
283298
}
@@ -287,16 +302,18 @@ async function getEmailTemplate(
287302
message: boolean,
288303
): Promise<{ subject: string; content: string }> {
289304
await loadEmailTemplates();
290-
305+
291306
const selectedLocale =
292307
negotiateLocale(locale, Object.keys(cachedAvailableLocales!)) ??
293308
new Intl.Locale("en");
294-
309+
295310
const template = cachedTemplates!.get(selectedLocale.baseName);
296311
if (!template) {
297-
throw new Error(`No email template found for locale ${selectedLocale.baseName}`);
312+
throw new Error(
313+
`No email template found for locale ${selectedLocale.baseName}`,
314+
);
298315
}
299-
316+
300317
return {
301318
subject: template.subject,
302319
content: message ? template.emailContentWithMessage : template.emailContent,

graphql/misc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ builder.queryField("availableLocales", (t) =>
1111
type: ["Locale"],
1212
async resolve(_root, _args, _ctx) {
1313
if (cachedLocales) return cachedLocales;
14-
14+
1515
const availableLocales: Intl.Locale[] = [];
1616
const files = expandGlob(join(LOCALES_DIR, "*.json"), {
1717
includeDirs: false,
@@ -27,7 +27,7 @@ builder.queryField("availableLocales", (t) =>
2727
// ignore invalid locale tags
2828
}
2929
}
30-
30+
3131
cachedLocales = availableLocales;
3232
return availableLocales;
3333
},

0 commit comments

Comments
 (0)