@@ -69,7 +69,7 @@ InvitationRef.implement({
69
69
} ) ;
70
70
71
71
const InviteInviterError = builder . enumType ( "InviteInviterError" , {
72
- values : [ "INVITER_NOT_AUTHENTICATED" , "INVITER_NO_INVITATIONS_LEFT" ] as const ,
72
+ values : [ "INVITER_NOT_AUTHENTICATED" , "INVITER_NO_INVITATIONS_LEFT" , "INVITER_EMAIL_SEND_FAILED" ] as const ,
73
73
} ) ;
74
74
75
75
const InviteEmailError = builder . enumType ( "InviteEmailError" , {
@@ -223,6 +223,15 @@ builder.mutationField("invite", (t) =>
223
223
"Failed to send invitation email: {errors}" ,
224
224
{ errors : receipt . errorMessages } ,
225
225
) ;
226
+ // Credit back the invitation on email send failure
227
+ await ctx . db . update ( accountTable ) . set ( {
228
+ leftInvitations : sql `${ accountTable . leftInvitations } + 1` ,
229
+ } ) . where ( eq ( accountTable . id , ctx . account . id ) ) ;
230
+
231
+ // Return validation error to inform the user
232
+ return {
233
+ inviter : "INVITER_EMAIL_SEND_FAILED" ,
234
+ } satisfies InviteValidationErrors ;
226
235
}
227
236
return {
228
237
inviterId : ctx . account . id ,
@@ -235,32 +244,62 @@ builder.mutationField("invite", (t) =>
235
244
236
245
const LOCALES_DIR = join ( import . meta. dirname ! , "locales" ) ;
237
246
238
- async function getEmailTemplate (
239
- locale : Intl . Locale ,
240
- message : boolean ,
241
- ) : Promise < { subject : string ; content : string } > {
247
+ // Cache for email templates
248
+ let cachedTemplates : Map < string , { subject : string ; emailContent : string ; emailContentWithMessage : string } > | null = null ;
249
+ let cachedAvailableLocales : Record < string , string > | null = null ;
250
+
251
+ async function loadEmailTemplates ( ) : Promise < void > {
252
+ if ( cachedTemplates && cachedAvailableLocales ) return ;
253
+
242
254
const availableLocales : Record < string , string > = { } ;
255
+ const templates = new Map < string , { subject : string ; emailContent : string ; emailContentWithMessage : string } > ( ) ;
256
+
243
257
const files = expandGlob ( join ( LOCALES_DIR , "*.json" ) , {
244
258
includeDirs : false ,
245
259
} ) ;
260
+
246
261
for await ( const file of files ) {
247
262
if ( ! file . isFile ) continue ;
248
263
const match = file . name . match ( / ^ ( .+ ) \. j s o n $ / ) ;
249
264
if ( match == null ) continue ;
250
265
const localeName = match [ 1 ] ;
251
266
availableLocales [ localeName ] = file . path ;
267
+
268
+ try {
269
+ const json = await Deno . readTextFile ( file . path ) ;
270
+ const data = JSON . parse ( json ) ;
271
+ templates . set ( localeName , {
272
+ subject : data . invite . emailSubject ,
273
+ emailContent : data . invite . emailContent ,
274
+ emailContentWithMessage : data . invite . emailContentWithMessage ,
275
+ } ) ;
276
+ } catch ( error ) {
277
+ console . warn ( `Failed to load email template for locale ${ localeName } :` , error ) ;
278
+ }
252
279
}
280
+
281
+ cachedTemplates = templates ;
282
+ cachedAvailableLocales = availableLocales ;
283
+ }
284
+
285
+ async function getEmailTemplate (
286
+ locale : Intl . Locale ,
287
+ message : boolean ,
288
+ ) : Promise < { subject : string ; content : string } > {
289
+ await loadEmailTemplates ( ) ;
290
+
253
291
const selectedLocale =
254
- negotiateLocale ( locale , Object . keys ( availableLocales ) ) ??
292
+ negotiateLocale ( locale , Object . keys ( cachedAvailableLocales ! ) ) ??
255
293
new Intl . Locale ( "en" ) ;
256
- const path = availableLocales [ selectedLocale . baseName ] ;
257
- const json = await Deno . readTextFile ( path ) ;
258
- const data = JSON . parse ( json ) ;
294
+
295
+ const template = cachedTemplates ! . get ( selectedLocale . baseName ) ;
296
+ if ( ! template ) {
297
+ throw new Error ( `No email template found for locale ${ selectedLocale . baseName } ` ) ;
298
+ }
299
+
259
300
return {
260
- subject : data . invite . emailSubject ,
261
- content : message
262
- ? data . invite . emailContentWithMessage
263
- : data . invite . emailContent ,
301
+ subject : template . subject ,
302
+ content : message ? template . emailContentWithMessage : template . emailContent ,
264
303
} ;
265
304
}
266
305
0 commit comments