@contember/engine-tenant-api 2.0.0 → 2.1.0-alpha.1

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.
Files changed (40) hide show
  1. package/dist/development/index.cjs +1 -0
  2. package/dist/development/index.cjs.map +1 -1
  3. package/dist/development/index.js +2 -1
  4. package/dist/development/src/model/mailing/UserMailer.cjs +18 -12
  5. package/dist/development/src/model/mailing/UserMailer.cjs.map +1 -1
  6. package/dist/development/src/model/mailing/UserMailer.js +18 -12
  7. package/dist/development/src/model/mailing/UserMailer.js.map +1 -1
  8. package/dist/development/src/model/service/InviteManager.cjs +2 -2
  9. package/dist/development/src/model/service/InviteManager.cjs.map +1 -1
  10. package/dist/development/src/model/service/InviteManager.js +2 -2
  11. package/dist/development/src/model/service/InviteManager.js.map +1 -1
  12. package/dist/development/src/utils/Mailer.cjs +29 -1
  13. package/dist/development/src/utils/Mailer.cjs.map +1 -1
  14. package/dist/development/src/utils/Mailer.js +29 -1
  15. package/dist/development/src/utils/Mailer.js.map +1 -1
  16. package/dist/production/index.cjs +1 -0
  17. package/dist/production/index.cjs.map +1 -1
  18. package/dist/production/index.js +2 -1
  19. package/dist/production/src/model/mailing/UserMailer.cjs +18 -12
  20. package/dist/production/src/model/mailing/UserMailer.cjs.map +1 -1
  21. package/dist/production/src/model/mailing/UserMailer.js +18 -12
  22. package/dist/production/src/model/mailing/UserMailer.js.map +1 -1
  23. package/dist/production/src/model/service/InviteManager.cjs +2 -2
  24. package/dist/production/src/model/service/InviteManager.cjs.map +1 -1
  25. package/dist/production/src/model/service/InviteManager.js +2 -2
  26. package/dist/production/src/model/service/InviteManager.js.map +1 -1
  27. package/dist/production/src/utils/Mailer.cjs +29 -1
  28. package/dist/production/src/utils/Mailer.cjs.map +1 -1
  29. package/dist/production/src/utils/Mailer.js +29 -1
  30. package/dist/production/src/utils/Mailer.js.map +1 -1
  31. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  32. package/dist/types/model/mailing/UserMailer.d.ts +4 -1
  33. package/dist/types/model/mailing/UserMailer.d.ts.map +1 -1
  34. package/dist/types/tsconfig.tsbuildinfo +1 -1
  35. package/dist/types/utils/Mailer.d.ts +24 -4
  36. package/dist/types/utils/Mailer.d.ts.map +1 -1
  37. package/package.json +12 -12
  38. package/src/model/mailing/UserMailer.ts +23 -15
  39. package/src/model/service/InviteManager.ts +2 -2
  40. package/src/utils/Mailer.ts +56 -6
@@ -18,43 +18,49 @@ class UserMailer {
18
18
  this.templateRenderer = templateRenderer;
19
19
  }
20
20
  async sendNewUserInvitedMail(dbContext, mailArguments, customMailOptions) {
21
- const template = await this.getCustomTemplate(dbContext, { type: MailType.newUserInvited, ...customMailOptions }) || {
21
+ const templateId = { type: MailType.newUserInvited, ...customMailOptions };
22
+ const template = await this.getCustomTemplate(dbContext, templateId) || {
22
23
  subject: "You have been invited to {{project}}",
23
24
  content: NewUserInvited,
24
25
  replyTo: null
25
26
  };
26
- await this.sendTemplate(template, mailArguments);
27
+ await this.sendMail(templateId, template, mailArguments);
27
28
  }
28
29
  async sendExistingUserInvitedEmail(dbContext, mailArguments, customMailOptions) {
29
- const template = await this.getCustomTemplate(dbContext, { type: MailType.existingUserInvited, ...customMailOptions }) || {
30
+ const templateId = { type: MailType.existingUserInvited, ...customMailOptions };
31
+ const template = await this.getCustomTemplate(dbContext, templateId) || {
30
32
  subject: "You have been invited to {{project}}",
31
33
  content: ExistingUserInvited,
32
34
  replyTo: null
33
35
  };
34
- await this.sendTemplate(template, mailArguments);
36
+ await this.sendMail(templateId, template, mailArguments);
35
37
  }
36
38
  async sendPasswordResetEmail(dbContext, mailArguments, customMailOptions) {
37
- const template = await this.getCustomTemplate(dbContext, { type: MailType.passwordReset, ...customMailOptions }) || {
39
+ const templateId = { type: MailType.passwordReset, ...customMailOptions };
40
+ const template = await this.getCustomTemplate(dbContext, templateId) || {
38
41
  subject: "Password reset",
39
42
  content: PasswordReset,
40
43
  replyTo: null
41
44
  };
42
- await this.sendTemplate(template, mailArguments);
45
+ await this.sendMail(templateId, template, mailArguments);
43
46
  }
44
47
  async sendPasswordlessEmail(dbContext, mailArguments, customMailOptions) {
45
- const template = await this.getCustomTemplate(dbContext, { type: MailType.passwordlessSignIn, ...customMailOptions }) || {
48
+ const templateId = { type: MailType.passwordlessSignIn, ...customMailOptions };
49
+ const template = await this.getCustomTemplate(dbContext, templateId) || {
46
50
  subject: "Sign in here",
47
51
  content: PasswordlessSignIn,
48
52
  replyTo: null
49
53
  };
50
- await this.sendTemplate(template, mailArguments);
54
+ await this.sendMail(templateId, template, mailArguments);
51
55
  }
52
- async sendTemplate(template, mailArguments) {
53
- const html = await this.templateRenderer.render(template.content, mailArguments);
56
+ async sendMail(templateId, template, variables) {
57
+ const html = await this.templateRenderer.render(template.content, variables);
54
58
  await this.mailer.send({
55
- to: mailArguments.email,
56
- subject: await this.templateRenderer.render(template.subject, mailArguments),
59
+ to: variables.email,
60
+ subject: await this.templateRenderer.render(template.subject, variables),
57
61
  html,
62
+ variables,
63
+ template: templateId,
58
64
  ...template.replyTo ? {
59
65
  replyTo: template.replyTo
60
66
  } : {}
@@ -1 +1 @@
1
- {"version":3,"file":"UserMailer.js","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst template = (await this.getCustomTemplate(dbContext, { type: MailType.newUserInvited, ...customMailOptions })) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: NewUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendTemplate(template, mailArguments)\n\t}\n\n\tasync sendExistingUserInvitedEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; project: string; projectSlug: string },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst template = (await this.getCustomTemplate(dbContext, { type: MailType.existingUserInvited, ...customMailOptions })) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: ExistingUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendTemplate(template, mailArguments)\n\t}\n\n\tasync sendPasswordResetEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst template = (await this.getCustomTemplate(dbContext, { type: MailType.passwordReset, ...customMailOptions })) || {\n\t\t\tsubject: 'Password reset',\n\t\t\tcontent: PasswordReset,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendTemplate(template, mailArguments)\n\t}\n\n\tasync sendPasswordlessEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string; url?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst template = (await this.getCustomTemplate(dbContext, { type: MailType.passwordlessSignIn, ...customMailOptions })) || {\n\t\t\tsubject: 'Sign in here',\n\t\t\tcontent: PasswordlessSignIn,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendTemplate(template, mailArguments)\n\t}\n\n\tprivate async sendTemplate(\n\t\ttemplate: Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'>,\n\t\tmailArguments: Record<string, any>,\n\t) {\n\t\tconst html = await this.templateRenderer.render(template.content, mailArguments)\n\t\tawait this.mailer.send({\n\t\t\tto: mailArguments.email,\n\t\t\tsubject: await this.templateRenderer.render(template.subject, mailArguments),\n\t\t\thtml,\n\t\t\t...(template.replyTo ? {\n\t\t\t\treplyTo: template.replyTo,\n\t\t\t} : {}),\n\t\t})\n\t}\n\n\tprivate async getCustomTemplate(\n\t\tdbContext: DatabaseContext,\n\t\tidentifier: MailTemplateIdentifier,\n\t): Promise<Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'> | null> {\n\t\tconst customTemplate =\n\t\t\t(await dbContext.queryHandler.fetch(new MailTemplateQuery(identifier)))\n\t\t\t?? (await dbContext.queryHandler.fetch(new MailTemplateQuery({ ...identifier, projectId: null })))\n\n\t\tif (!customTemplate) {\n\t\t\treturn null\n\t\t}\n\t\tconst content = customTemplate.useLayout ? Layout(customTemplate.content) : customTemplate.content\n\t\treturn { content, subject: customTemplate.subject, replyTo: customTemplate.replyTo }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAUO,MAAM,WAAW;AAAA,EACvB,YACkB,QACA,kBAChB;AAFgB,SAAA,SAAA;AACA,SAAA,mBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,EAAE,MAAM,SAAS,gBAAgB,GAAG,kBAAmB,CAAA,KAAM;AAAA,MACtH,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACM,UAAA,KAAK,aAAa,UAAU,aAAa;AAAA,EAAA;AAAA,EAGhD,MAAM,6BACL,WACA,eACA,mBACgB;AAChB,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,EAAE,MAAM,SAAS,qBAAqB,GAAG,kBAAmB,CAAA,KAAM;AAAA,MAC3H,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACM,UAAA,KAAK,aAAa,UAAU,aAAa;AAAA,EAAA;AAAA,EAGhD,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,EAAE,MAAM,SAAS,eAAe,GAAG,kBAAmB,CAAA,KAAM;AAAA,MACrH,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACM,UAAA,KAAK,aAAa,UAAU,aAAa;AAAA,EAAA;AAAA,EAGhD,MAAM,sBACL,WACA,eACA,mBACgB;AAChB,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,EAAE,MAAM,SAAS,oBAAoB,GAAG,kBAAmB,CAAA,KAAM;AAAA,MAC1H,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACM,UAAA,KAAK,aAAa,UAAU,aAAa;AAAA,EAAA;AAAA,EAGhD,MAAc,aACb,UACA,eACC;AACD,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,aAAa;AACzE,UAAA,KAAK,OAAO,KAAK;AAAA,MACtB,IAAI,cAAc;AAAA,MAClB,SAAS,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,aAAa;AAAA,MAC3E;AAAA,MACA,GAAI,SAAS,UAAU;AAAA,QACtB,SAAS,SAAS;AAAA,MAAA,IACf,CAAA;AAAA,IAAC,CACL;AAAA,EAAA;AAAA,EAGF,MAAc,kBACb,WACA,YAC4E;AACtE,UAAA,iBACJ,MAAM,UAAU,aAAa,MAAM,IAAI,kBAAkB,UAAU,CAAC,KACjE,MAAM,UAAU,aAAa,MAAM,IAAI,kBAAkB,EAAE,GAAG,YAAY,WAAW,KAAK,CAAC,CAAC;AAEjG,QAAI,CAAC,gBAAgB;AACb,aAAA;AAAA,IAAA;AAER,UAAM,UAAU,eAAe,YAAY,OAAO,eAAe,OAAO,IAAI,eAAe;AAC3F,WAAO,EAAE,SAAS,SAAS,eAAe,SAAS,SAAS,eAAe,QAAQ;AAAA,EAAA;AAErF;"}
1
+ {"version":3,"file":"UserMailer.js","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\nimport { Acl } from '@contember/schema'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string; memberships: readonly Acl.Membership[]},\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.newUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: NewUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendExistingUserInvitedEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; project: string; projectSlug: string; memberships: readonly Acl.Membership[] },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.existingUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: ExistingUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordResetEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordReset, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Password reset',\n\t\t\tcontent: PasswordReset,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordlessEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string; url?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordlessSignIn, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Sign in here',\n\t\t\tcontent: PasswordlessSignIn,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tprivate async sendMail(\n\t\ttemplateId: MailTemplateIdentifier,\n\t\ttemplate: Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'>,\n\t\tvariables: Record<string, any>,\n\t) {\n\t\tconst html = await this.templateRenderer.render(template.content, variables)\n\t\tawait this.mailer.send({\n\t\t\tto: variables.email,\n\t\t\tsubject: await this.templateRenderer.render(template.subject, variables),\n\t\t\thtml,\n\t\t\tvariables,\n\t\t\ttemplate: templateId,\n\t\t\t...(template.replyTo ? {\n\t\t\t\treplyTo: template.replyTo,\n\t\t\t} : {}),\n\t\t})\n\t}\n\n\tprivate async getCustomTemplate(\n\t\tdbContext: DatabaseContext,\n\t\tidentifier: MailTemplateIdentifier,\n\t): Promise<Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'> | null> {\n\t\tconst customTemplate =\n\t\t\t(await dbContext.queryHandler.fetch(new MailTemplateQuery(identifier)))\n\t\t\t?? (await dbContext.queryHandler.fetch(new MailTemplateQuery({ ...identifier, projectId: null })))\n\n\t\tif (!customTemplate) {\n\t\t\treturn null\n\t\t}\n\t\tconst content = customTemplate.useLayout ? Layout(customTemplate.content) : customTemplate.content\n\t\treturn { content, subject: customTemplate.subject, replyTo: customTemplate.replyTo }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAWO,MAAM,WAAW;AAAA,EACvB,YACkB,QACA,kBAChB;AAFgB,SAAA,SAAA;AACA,SAAA,mBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,gBAAgB,GAAG,kBAAkB;AACzE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,6BACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,qBAAqB,GAAG,kBAAkB;AAC9E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,eAAe,GAAG,kBAAkB;AACxE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,sBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,oBAAoB,GAAG,kBAAkB;AAC7E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAc,SACb,YACA,UACA,WACC;AACD,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AACrE,UAAA,KAAK,OAAO,KAAK;AAAA,MACtB,IAAI,UAAU;AAAA,MACd,SAAS,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AAAA,MACvE;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,GAAI,SAAS,UAAU;AAAA,QACtB,SAAS,SAAS;AAAA,MAAA,IACf,CAAA;AAAA,IAAC,CACL;AAAA,EAAA;AAAA,EAGF,MAAc,kBACb,WACA,YAC4E;AACtE,UAAA,iBACJ,MAAM,UAAU,aAAa,MAAM,IAAI,kBAAkB,UAAU,CAAC,KACjE,MAAM,UAAU,aAAa,MAAM,IAAI,kBAAkB,EAAE,GAAG,YAAY,WAAW,KAAK,CAAC,CAAC;AAEjG,QAAI,CAAC,gBAAgB;AACb,aAAA;AAAA,IAAA;AAER,UAAM,UAAU,eAAe,YAAY,OAAO,eAAe,OAAO,IAAI,eAAe;AAC3F,WAAO,EAAE,SAAS,SAAS,eAAe,SAAS,SAAS,eAAe,QAAQ;AAAA,EAAA;AAErF;"}
@@ -75,11 +75,11 @@ class InviteManager {
75
75
  if (isNew) {
76
76
  await this.mailer.sendNewUserInvitedMail(
77
77
  trx,
78
- { email: email$1, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },
78
+ { email: email$1, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },
79
79
  customMailOptions
80
80
  );
81
81
  } else {
82
- await this.mailer.sendExistingUserInvitedEmail(trx, { email: email$1, project: project.name, projectSlug: project.slug }, customMailOptions);
82
+ await this.mailer.sendExistingUserInvitedEmail(trx, { email: email$1, project: project.name, projectSlug: project.slug, memberships }, customMailOptions);
83
83
  }
84
84
  }
85
85
  return new Response.ResponseOk(new InviteResult(person, isNew));
@@ -1 +1 @@
1
- {"version":3,"file":"InviteManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":["email","validateEmail","ResponseError","PersonQuery","CreateIdentityCommand","TenantRole","PasswordPlain","NoPassword","CreatePersonCommand","CreatePersonTokenCommand","SavePersonTokenCommand","createAppendMembershipVariables","CreateOrUpdateProjectMembershipCommand","ResponseOk","INVITATION_RESET_TOKEN_EXPIRATION_MINUTES"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IAAA,OACCA;AAAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAACC,MAAA,cAAcD,QAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAIE,SAAAA,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAMC,wBAAY,QAAQH,OAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAII,sBAAAA,sBAAsB,CAACC,MAAAA,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAIC,SAAA,cAAc,iBAAiB,IAAIC,SAAA;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAIC,oBAAAA,oBAAoB;AAAA,UAC7D;AAAA,UAAA,OACAR;AAAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQS,yBAAyB,yBAAA,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAIC,8CAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoBC,gDAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAIC,uCAAA,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAA,OAAEZ,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,WAAW;AAAA,YAC1G;AAAA,UACD;AAAA,QAAA,OACM;AACN,gBAAM,KAAK,OAAO,6BAA6B,KAAK,EAAEA,OAAAA,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,KAAA,GAAQ,iBAAiB;AAAA,QAAA;AAAA,MACnI;AAED,aAAO,IAAIa,SAAAA,WAAW,IAAI,aAAa,QAAQ,KAAK,CAAC;AAAA,IAAA,CACrD;AAAA,EAAA;AAAA,EAGF,MAAc,oBAAoB,aAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,sBAAsB,UAAU,WAAW;AAE9D,WAAA,QAAQ,SAAS,QAAQ,2BAA2BC,YAAA;AAAA,EAAA;AAE7D;AAIO,MAAM,aAAa;AAAA,EACzB,YAA4B,QAAkD,OAAgB;AAAlE,SAAA,SAAA;AAAkD,SAAA,QAAA;AAAA,EAAA;AAC/E;;;"}
1
+ {"version":3,"file":"InviteManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug, memberships }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":["email","validateEmail","ResponseError","PersonQuery","CreateIdentityCommand","TenantRole","PasswordPlain","NoPassword","CreatePersonCommand","CreatePersonTokenCommand","SavePersonTokenCommand","createAppendMembershipVariables","CreateOrUpdateProjectMembershipCommand","ResponseOk","INVITATION_RESET_TOKEN_EXPIRATION_MINUTES"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IAAA,OACCA;AAAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAACC,MAAA,cAAcD,QAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAIE,SAAAA,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAMC,wBAAY,QAAQH,OAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAII,sBAAAA,sBAAsB,CAACC,MAAAA,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAIC,SAAA,cAAc,iBAAiB,IAAIC,SAAA;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAIC,oBAAAA,oBAAoB;AAAA,UAC7D;AAAA,UAAA,OACAR;AAAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQS,yBAAyB,yBAAA,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAIC,8CAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoBC,gDAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAIC,uCAAA,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAA,OAAEZ,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,YAAY,YAAY;AAAA,YACvH;AAAA,UACD;AAAA,QAAA,OACM;AACN,gBAAM,KAAK,OAAO,6BAA6B,KAAK,EAAA,OAAEA,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,YAAA,GAAe,iBAAiB;AAAA,QAAA;AAAA,MAChJ;AAED,aAAO,IAAIa,SAAAA,WAAW,IAAI,aAAa,QAAQ,KAAK,CAAC;AAAA,IAAA,CACrD;AAAA,EAAA;AAAA,EAGF,MAAc,oBAAoB,aAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,sBAAsB,UAAU,WAAW;AAE9D,WAAA,QAAQ,SAAS,QAAQ,2BAA2BC,YAAA;AAAA,EAAA;AAE7D;AAIO,MAAM,aAAa;AAAA,EACzB,YAA4B,QAAkD,OAAgB;AAAlE,SAAA,SAAA;AAAkD,SAAA,QAAA;AAAA,EAAA;AAC/E;;;"}
@@ -73,11 +73,11 @@ class InviteManager {
73
73
  if (isNew) {
74
74
  await this.mailer.sendNewUserInvitedMail(
75
75
  trx,
76
- { email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },
76
+ { email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },
77
77
  customMailOptions
78
78
  );
79
79
  } else {
80
- await this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug }, customMailOptions);
80
+ await this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug, memberships }, customMailOptions);
81
81
  }
82
82
  }
83
83
  return new ResponseOk(new InviteResult(person, isNew));
@@ -1 +1 @@
1
- {"version":3,"file":"InviteManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAAC,cAAc,MAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAI,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAM,YAAY,QAAQ,KAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAI,sBAAsB,CAAC,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAI,cAAc,iBAAiB,IAAI;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAI,oBAAoB;AAAA,UAC7D;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQ,yBAAyB,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAI,uBAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoB,gCAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAI,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,WAAW;AAAA,YAC1G;AAAA,UACD;AAAA,QAAA,OACM;AACN,gBAAM,KAAK,OAAO,6BAA6B,KAAK,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,KAAA,GAAQ,iBAAiB;AAAA,QAAA;AAAA,MACnI;AAED,aAAO,IAAI,WAAW,IAAI,aAAa,QAAQ,KAAK,CAAC;AAAA,IAAA,CACrD;AAAA,EAAA;AAAA,EAGF,MAAc,oBAAoB,aAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,sBAAsB,UAAU,WAAW;AAE9D,WAAA,QAAQ,SAAS,QAAQ,2BAA2B;AAAA,EAAA;AAE7D;AAIO,MAAM,aAAa;AAAA,EACzB,YAA4B,QAAkD,OAAgB;AAAlE,SAAA,SAAA;AAAkD,SAAA,QAAA;AAAA,EAAA;AAC/E;"}
1
+ {"version":3,"file":"InviteManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug, memberships }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAAC,cAAc,MAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAI,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAM,YAAY,QAAQ,KAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAI,sBAAsB,CAAC,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAI,cAAc,iBAAiB,IAAI;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAI,oBAAoB;AAAA,UAC7D;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQ,yBAAyB,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAI,uBAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoB,gCAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAI,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,YAAY,YAAY;AAAA,YACvH;AAAA,UACD;AAAA,QAAA,OACM;AACN,gBAAM,KAAK,OAAO,6BAA6B,KAAK,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,YAAA,GAAe,iBAAiB;AAAA,QAAA;AAAA,MAChJ;AAED,aAAO,IAAI,WAAW,IAAI,aAAa,QAAQ,KAAK,CAAC;AAAA,IAAA,CACrD;AAAA,EAAA;AAAA,EAGF,MAAc,oBAAoB,aAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,sBAAsB,UAAU,WAAW;AAE9D,WAAA,QAAQ,SAAS,QAAQ,2BAA2B;AAAA,EAAA;AAE7D;AAIO,MAAM,aAAa;AAAA,EACzB,YAA4B,QAAkD,OAAgB;AAAlE,SAAA,SAAA;AAAkD,SAAA,QAAA;AAAA,EAAA;AAC/E;"}
@@ -7,7 +7,7 @@ class NodeMailer {
7
7
  this.from = from;
8
8
  this.transport = transport;
9
9
  }
10
- async send(message) {
10
+ async send({ variables, template, ...message }) {
11
11
  try {
12
12
  return await this.transport.sendMail({ ...message, from: message.from || this.from });
13
13
  } catch (e) {
@@ -16,10 +16,38 @@ class NodeMailer {
16
16
  }
17
17
  }
18
18
  }
19
+ class WebhookMailer {
20
+ constructor(from, url, headers = {}) {
21
+ this.from = from;
22
+ this.url = url;
23
+ this.headers = headers;
24
+ }
25
+ async send(message) {
26
+ const response = await fetch(this.url, {
27
+ method: "POST",
28
+ body: JSON.stringify({ ...message, from: message.from || this.from }),
29
+ headers: {
30
+ ...this.headers,
31
+ "Content-Type": "application/json"
32
+ }
33
+ });
34
+ if (!response.ok) {
35
+ logger.logger.error("Mail sending failed", { mail: message, response });
36
+ return { error: response.statusText };
37
+ }
38
+ await response.text();
39
+ return {};
40
+ }
41
+ }
19
42
  const createMailer = (options) => {
20
43
  const { from, ...transportOptions } = options;
44
+ if ("webhook" in transportOptions) {
45
+ const config = typeof transportOptions.webhook === "string" ? { url: transportOptions.webhook } : transportOptions.webhook;
46
+ return new WebhookMailer(from, config.url, config.headers);
47
+ }
21
48
  return new NodeMailer(from, nodemailer.createTransport(transportOptions));
22
49
  };
23
50
  exports.NodeMailer = NodeMailer;
51
+ exports.WebhookMailer = WebhookMailer;
24
52
  exports.createMailer = createMailer;
25
53
  //# sourceMappingURL=Mailer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Mailer.cjs","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport { Options as MailMessage } from 'nodemailer/lib/mailer'\nimport * as SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport * as SMTPPool from 'nodemailer/lib/smtp-pool'\nimport * as SendmailTransport from 'nodemailer/lib/sendmail-transport'\nimport * as SESTransport from 'nodemailer/lib/ses-transport'\nimport { logger } from '@contember/logger'\n\nexport { type MailMessage }\n\nexport interface SentInfo {\n\terror?: any\n\tmessageId?: string\n\tenvelope?: any\n\taccepted?: string[]\n\trejected?: string[]\n\tpending?: string[]\n}\n\nexport type MailerOptions = Partial<\n\t(SMTPTransport.Options | SMTPPool.Options | SendmailTransport.Options | SESTransport.Options | TransportOptions) & {\n\t\tfrom?: string\n\t}\n>\n\nexport interface Mailer {\n\tsend(message: MailMessage): Promise<SentInfo>\n}\n\nexport class NodeMailer implements Mailer {\n\tconstructor(private readonly from: string | undefined, private readonly transport: Transporter) {}\n\n\tpublic async send(message: MailMessage): Promise<SentInfo> {\n\t\ttry {\n\t\t\treturn await this.transport.sendMail({ ...message, from: message.from || this.from })\n\t\t} catch (e) {\n\t\t\tlogger.error(e, { message: 'Mail sending failed', mail: message })\n\t\t\treturn { error: e }\n\t\t}\n\t}\n}\n\nexport const createMailer = (options: MailerOptions): Mailer => {\n\tconst { from, ...transportOptions } = options\n\treturn new NodeMailer(from, createTransport(transportOptions as any))\n}\n"],"names":["logger","createTransport"],"mappings":";;;;AA6BO,MAAM,WAA6B;AAAA,EACzC,YAA6B,MAA2C,WAAwB;AAAnE,SAAA,OAAA;AAA2C,SAAA,YAAA;AAAA,EAAA;AAAA,EAExE,MAAa,KAAK,SAAyC;AACtD,QAAA;AACH,aAAO,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,KAAA,CAAM;AAAA,aAC5E,GAAG;AACXA,aAAA,OAAO,MAAM,GAAG,EAAE,SAAS,uBAAuB,MAAM,SAAS;AAC1D,aAAA,EAAE,OAAO,EAAE;AAAA,IAAA;AAAA,EACnB;AAEF;AAEa,MAAA,eAAe,CAAC,YAAmC;AAC/D,QAAM,EAAE,MAAM,GAAG,iBAAA,IAAqB;AACtC,SAAO,IAAI,WAAW,MAAMC,WAAA,gBAAgB,gBAAuB,CAAC;AACrE;;;"}
1
+ {"version":3,"file":"Mailer.cjs","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport * as SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport * as SMTPPool from 'nodemailer/lib/smtp-pool'\nimport * as SendmailTransport from 'nodemailer/lib/sendmail-transport'\nimport * as SESTransport from 'nodemailer/lib/ses-transport'\nimport { logger } from '@contember/logger'\nimport { MailTemplateIdentifier } from '../model'\n\nexport interface MailMessage {\n\tto: string\n\tsubject: string\n\thtml: string\n\tfrom?: string\n\treplyTo?: string\n\tvariables?: Record<string, any>\n\ttemplate?: MailTemplateIdentifier\n}\n\nexport interface SentInfo {\n\terror?: any\n\tmessageId?: string\n\tenvelope?: any\n\taccepted?: string[]\n\trejected?: string[]\n\tpending?: string[]\n}\n\nexport type MailerOptions =\n\t& (\n\t\tSMTPTransport.Options | SMTPPool.Options | SendmailTransport.Options | SESTransport.Options | TransportOptions\n\t\t| {\n\t\t\twebhook: string | {\n\t\t\t\turl: string\n\t\t\t\theaders?: Record<string, string>\n\t\t\t}\n\t\t}\n\t)\n\t& {\n\t\tfrom?: string\n\t}\n\n\nexport interface Mailer {\n\tsend(message: MailMessage): Promise<SentInfo>\n}\n\nexport class NodeMailer implements Mailer {\n\tconstructor(private readonly from: string | undefined, private readonly transport: Transporter) {}\n\n\tpublic async send({ variables, template, ...message }: MailMessage): Promise<SentInfo> {\n\t\ttry {\n\t\t\treturn await this.transport.sendMail({ ...message, from: message.from || this.from })\n\t\t} catch (e) {\n\t\t\tlogger.error(e, { message: 'Mail sending failed', mail: message })\n\t\t\treturn { error: e }\n\t\t}\n\t}\n}\n\nexport class WebhookMailer implements Mailer {\n\n\tconstructor(\n\t\tprivate readonly from: string | undefined,\n\t\tprivate readonly url: string,\n\t\tprivate readonly headers: Record<string, string> = {},\n\t) {\n\t}\n\n\tpublic async send(message: MailMessage): Promise<SentInfo> {\n\t\tconst response = await fetch(this.url, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: JSON.stringify({ ...message, from: message.from || this.from }),\n\t\t\theaders: {\n\t\t\t\t...this.headers,\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t})\n\t\tif (!response.ok) {\n\t\t\tlogger.error('Mail sending failed', { mail: message, response })\n\t\t\treturn { error: response.statusText }\n\t\t}\n\t\tawait response.text()\n\t\treturn {}\n\t}\n}\n\n\nexport const createMailer = (options: MailerOptions): Mailer => {\n\tconst { from, ...transportOptions } = options\n\tif ('webhook' in transportOptions) {\n\t\tconst config = typeof transportOptions.webhook === 'string' ? { url: transportOptions.webhook } : transportOptions.webhook\n\t\treturn new WebhookMailer(from, config.url, config.headers)\n\t}\n\n\treturn new NodeMailer(from, createTransport(transportOptions as any))\n}\n"],"names":["logger","createTransport"],"mappings":";;;;AA8CO,MAAM,WAA6B;AAAA,EACzC,YAA6B,MAA2C,WAAwB;AAAnE,SAAA,OAAA;AAA2C,SAAA,YAAA;AAAA,EAAA;AAAA,EAExE,MAAa,KAAK,EAAE,WAAW,UAAU,GAAG,WAA2C;AAClF,QAAA;AACH,aAAO,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,KAAA,CAAM;AAAA,aAC5E,GAAG;AACXA,aAAA,OAAO,MAAM,GAAG,EAAE,SAAS,uBAAuB,MAAM,SAAS;AAC1D,aAAA,EAAE,OAAO,EAAE;AAAA,IAAA;AAAA,EACnB;AAEF;AAEO,MAAM,cAAgC;AAAA,EAE5C,YACkB,MACA,KACA,UAAkC,CAAA,GAClD;AAHgB,SAAA,OAAA;AACA,SAAA,MAAA;AACA,SAAA,UAAA;AAAA,EAAA;AAAA,EAIlB,MAAa,KAAK,SAAyC;AAC1D,UAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAAA,MACtC,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,MAAM;AAAA,MACpE,SAAS;AAAA,QACR,GAAG,KAAK;AAAA,QACR,gBAAgB;AAAA,MAAA;AAAA,IACjB,CACA;AACG,QAAA,CAAC,SAAS,IAAI;AACjBA,aAAA,OAAO,MAAM,uBAAuB,EAAE,MAAM,SAAS,UAAU;AACxD,aAAA,EAAE,OAAO,SAAS,WAAW;AAAA,IAAA;AAErC,UAAM,SAAS,KAAK;AACpB,WAAO,CAAC;AAAA,EAAA;AAEV;AAGa,MAAA,eAAe,CAAC,YAAmC;AAC/D,QAAM,EAAE,MAAM,GAAG,iBAAA,IAAqB;AACtC,MAAI,aAAa,kBAAkB;AAC5B,UAAA,SAAS,OAAO,iBAAiB,YAAY,WAAW,EAAE,KAAK,iBAAiB,YAAY,iBAAiB;AACnH,WAAO,IAAI,cAAc,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,EAAA;AAG1D,SAAO,IAAI,WAAW,MAAMC,WAAA,gBAAgB,gBAAuB,CAAC;AACrE;;;;"}
@@ -5,7 +5,7 @@ class NodeMailer {
5
5
  this.from = from;
6
6
  this.transport = transport;
7
7
  }
8
- async send(message) {
8
+ async send({ variables, template, ...message }) {
9
9
  try {
10
10
  return await this.transport.sendMail({ ...message, from: message.from || this.from });
11
11
  } catch (e) {
@@ -14,12 +14,40 @@ class NodeMailer {
14
14
  }
15
15
  }
16
16
  }
17
+ class WebhookMailer {
18
+ constructor(from, url, headers = {}) {
19
+ this.from = from;
20
+ this.url = url;
21
+ this.headers = headers;
22
+ }
23
+ async send(message) {
24
+ const response = await fetch(this.url, {
25
+ method: "POST",
26
+ body: JSON.stringify({ ...message, from: message.from || this.from }),
27
+ headers: {
28
+ ...this.headers,
29
+ "Content-Type": "application/json"
30
+ }
31
+ });
32
+ if (!response.ok) {
33
+ logger.error("Mail sending failed", { mail: message, response });
34
+ return { error: response.statusText };
35
+ }
36
+ await response.text();
37
+ return {};
38
+ }
39
+ }
17
40
  const createMailer = (options) => {
18
41
  const { from, ...transportOptions } = options;
42
+ if ("webhook" in transportOptions) {
43
+ const config = typeof transportOptions.webhook === "string" ? { url: transportOptions.webhook } : transportOptions.webhook;
44
+ return new WebhookMailer(from, config.url, config.headers);
45
+ }
19
46
  return new NodeMailer(from, createTransport(transportOptions));
20
47
  };
21
48
  export {
22
49
  NodeMailer,
50
+ WebhookMailer,
23
51
  createMailer
24
52
  };
25
53
  //# sourceMappingURL=Mailer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Mailer.js","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport { Options as MailMessage } from 'nodemailer/lib/mailer'\nimport * as SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport * as SMTPPool from 'nodemailer/lib/smtp-pool'\nimport * as SendmailTransport from 'nodemailer/lib/sendmail-transport'\nimport * as SESTransport from 'nodemailer/lib/ses-transport'\nimport { logger } from '@contember/logger'\n\nexport { type MailMessage }\n\nexport interface SentInfo {\n\terror?: any\n\tmessageId?: string\n\tenvelope?: any\n\taccepted?: string[]\n\trejected?: string[]\n\tpending?: string[]\n}\n\nexport type MailerOptions = Partial<\n\t(SMTPTransport.Options | SMTPPool.Options | SendmailTransport.Options | SESTransport.Options | TransportOptions) & {\n\t\tfrom?: string\n\t}\n>\n\nexport interface Mailer {\n\tsend(message: MailMessage): Promise<SentInfo>\n}\n\nexport class NodeMailer implements Mailer {\n\tconstructor(private readonly from: string | undefined, private readonly transport: Transporter) {}\n\n\tpublic async send(message: MailMessage): Promise<SentInfo> {\n\t\ttry {\n\t\t\treturn await this.transport.sendMail({ ...message, from: message.from || this.from })\n\t\t} catch (e) {\n\t\t\tlogger.error(e, { message: 'Mail sending failed', mail: message })\n\t\t\treturn { error: e }\n\t\t}\n\t}\n}\n\nexport const createMailer = (options: MailerOptions): Mailer => {\n\tconst { from, ...transportOptions } = options\n\treturn new NodeMailer(from, createTransport(transportOptions as any))\n}\n"],"names":[],"mappings":";;AA6BO,MAAM,WAA6B;AAAA,EACzC,YAA6B,MAA2C,WAAwB;AAAnE,SAAA,OAAA;AAA2C,SAAA,YAAA;AAAA,EAAA;AAAA,EAExE,MAAa,KAAK,SAAyC;AACtD,QAAA;AACH,aAAO,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,KAAA,CAAM;AAAA,aAC5E,GAAG;AACX,aAAO,MAAM,GAAG,EAAE,SAAS,uBAAuB,MAAM,SAAS;AAC1D,aAAA,EAAE,OAAO,EAAE;AAAA,IAAA;AAAA,EACnB;AAEF;AAEa,MAAA,eAAe,CAAC,YAAmC;AAC/D,QAAM,EAAE,MAAM,GAAG,iBAAA,IAAqB;AACtC,SAAO,IAAI,WAAW,MAAM,gBAAgB,gBAAuB,CAAC;AACrE;"}
1
+ {"version":3,"file":"Mailer.js","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport * as SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport * as SMTPPool from 'nodemailer/lib/smtp-pool'\nimport * as SendmailTransport from 'nodemailer/lib/sendmail-transport'\nimport * as SESTransport from 'nodemailer/lib/ses-transport'\nimport { logger } from '@contember/logger'\nimport { MailTemplateIdentifier } from '../model'\n\nexport interface MailMessage {\n\tto: string\n\tsubject: string\n\thtml: string\n\tfrom?: string\n\treplyTo?: string\n\tvariables?: Record<string, any>\n\ttemplate?: MailTemplateIdentifier\n}\n\nexport interface SentInfo {\n\terror?: any\n\tmessageId?: string\n\tenvelope?: any\n\taccepted?: string[]\n\trejected?: string[]\n\tpending?: string[]\n}\n\nexport type MailerOptions =\n\t& (\n\t\tSMTPTransport.Options | SMTPPool.Options | SendmailTransport.Options | SESTransport.Options | TransportOptions\n\t\t| {\n\t\t\twebhook: string | {\n\t\t\t\turl: string\n\t\t\t\theaders?: Record<string, string>\n\t\t\t}\n\t\t}\n\t)\n\t& {\n\t\tfrom?: string\n\t}\n\n\nexport interface Mailer {\n\tsend(message: MailMessage): Promise<SentInfo>\n}\n\nexport class NodeMailer implements Mailer {\n\tconstructor(private readonly from: string | undefined, private readonly transport: Transporter) {}\n\n\tpublic async send({ variables, template, ...message }: MailMessage): Promise<SentInfo> {\n\t\ttry {\n\t\t\treturn await this.transport.sendMail({ ...message, from: message.from || this.from })\n\t\t} catch (e) {\n\t\t\tlogger.error(e, { message: 'Mail sending failed', mail: message })\n\t\t\treturn { error: e }\n\t\t}\n\t}\n}\n\nexport class WebhookMailer implements Mailer {\n\n\tconstructor(\n\t\tprivate readonly from: string | undefined,\n\t\tprivate readonly url: string,\n\t\tprivate readonly headers: Record<string, string> = {},\n\t) {\n\t}\n\n\tpublic async send(message: MailMessage): Promise<SentInfo> {\n\t\tconst response = await fetch(this.url, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: JSON.stringify({ ...message, from: message.from || this.from }),\n\t\t\theaders: {\n\t\t\t\t...this.headers,\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t})\n\t\tif (!response.ok) {\n\t\t\tlogger.error('Mail sending failed', { mail: message, response })\n\t\t\treturn { error: response.statusText }\n\t\t}\n\t\tawait response.text()\n\t\treturn {}\n\t}\n}\n\n\nexport const createMailer = (options: MailerOptions): Mailer => {\n\tconst { from, ...transportOptions } = options\n\tif ('webhook' in transportOptions) {\n\t\tconst config = typeof transportOptions.webhook === 'string' ? { url: transportOptions.webhook } : transportOptions.webhook\n\t\treturn new WebhookMailer(from, config.url, config.headers)\n\t}\n\n\treturn new NodeMailer(from, createTransport(transportOptions as any))\n}\n"],"names":[],"mappings":";;AA8CO,MAAM,WAA6B;AAAA,EACzC,YAA6B,MAA2C,WAAwB;AAAnE,SAAA,OAAA;AAA2C,SAAA,YAAA;AAAA,EAAA;AAAA,EAExE,MAAa,KAAK,EAAE,WAAW,UAAU,GAAG,WAA2C;AAClF,QAAA;AACH,aAAO,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,KAAA,CAAM;AAAA,aAC5E,GAAG;AACX,aAAO,MAAM,GAAG,EAAE,SAAS,uBAAuB,MAAM,SAAS;AAC1D,aAAA,EAAE,OAAO,EAAE;AAAA,IAAA;AAAA,EACnB;AAEF;AAEO,MAAM,cAAgC;AAAA,EAE5C,YACkB,MACA,KACA,UAAkC,CAAA,GAClD;AAHgB,SAAA,OAAA;AACA,SAAA,MAAA;AACA,SAAA,UAAA;AAAA,EAAA;AAAA,EAIlB,MAAa,KAAK,SAAyC;AAC1D,UAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAAA,MACtC,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,MAAM;AAAA,MACpE,SAAS;AAAA,QACR,GAAG,KAAK;AAAA,QACR,gBAAgB;AAAA,MAAA;AAAA,IACjB,CACA;AACG,QAAA,CAAC,SAAS,IAAI;AACjB,aAAO,MAAM,uBAAuB,EAAE,MAAM,SAAS,UAAU;AACxD,aAAA,EAAE,OAAO,SAAS,WAAW;AAAA,IAAA;AAErC,UAAM,SAAS,KAAK;AACpB,WAAO,CAAC;AAAA,EAAA;AAEV;AAGa,MAAA,eAAe,CAAC,YAAmC;AAC/D,QAAM,EAAE,MAAM,GAAG,iBAAA,IAAqB;AACtC,MAAI,aAAa,kBAAkB;AAC5B,UAAA,SAAS,OAAO,iBAAiB,YAAY,WAAW,EAAE,KAAK,iBAAiB,YAAY,iBAAiB;AACnH,WAAO,IAAI,cAAc,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,EAAA;AAG1D,SAAO,IAAI,WAAW,MAAM,gBAAgB,gBAAuB,CAAC;AACrE;"}