@stamhoofd/backend 2.39.1 → 2.40.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.
Files changed (198) hide show
  1. package/eslint.config.mjs +5 -0
  2. package/index.ts +81 -74
  3. package/jest.config.cjs +10 -0
  4. package/migrations.ts +16 -14
  5. package/package.json +11 -11
  6. package/src/crons/clear-excel-cache.test.ts +48 -50
  7. package/src/crons/clear-excel-cache.ts +18 -18
  8. package/src/crons/setup-steps.ts +2 -2
  9. package/src/crons.ts +325 -306
  10. package/src/decoders/StringArrayDecoder.ts +7 -7
  11. package/src/decoders/StringNullableDecoder.ts +1 -2
  12. package/src/email-recipient-loaders/members.ts +22 -22
  13. package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
  14. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
  15. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
  16. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
  17. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
  18. package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
  19. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
  20. package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
  21. package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
  22. package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
  23. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
  24. package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
  25. package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
  26. package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
  27. package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
  28. package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
  29. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
  30. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
  31. package/src/endpoints/auth/SignupEndpoint.ts +37 -36
  32. package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
  33. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
  34. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
  35. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
  36. package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
  37. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
  38. package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
  39. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
  40. package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
  41. package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
  42. package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
  43. package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
  44. package/src/endpoints/global/files/GetFileCache.ts +13 -13
  45. package/src/endpoints/global/files/UploadFile.ts +51 -54
  46. package/src/endpoints/global/files/UploadImage.ts +53 -53
  47. package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
  48. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
  49. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
  50. package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
  51. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
  52. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
  53. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
  54. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
  55. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
  56. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
  57. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
  58. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
  59. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
  60. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
  61. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
  62. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
  63. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
  64. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
  65. package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
  66. package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
  67. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
  68. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
  69. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
  70. package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
  71. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
  72. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
  73. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
  74. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
  75. package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
  76. package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
  77. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
  78. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
  79. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
  80. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
  81. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
  82. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
  83. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
  84. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
  85. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
  86. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
  87. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
  88. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
  89. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
  90. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
  91. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
  92. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
  93. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
  94. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
  95. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
  96. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
  97. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
  98. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
  99. package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
  100. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
  101. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
  102. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
  103. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
  104. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
  105. package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
  106. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
  107. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
  108. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
  109. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
  110. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
  111. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
  112. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
  113. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
  114. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
  115. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
  116. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
  117. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
  118. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
  119. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
  120. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
  121. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
  122. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
  123. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
  124. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
  125. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
  126. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
  127. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
  128. package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
  129. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
  130. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
  131. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
  132. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
  133. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
  134. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
  135. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
  136. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
  137. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
  138. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
  139. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
  140. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
  141. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
  142. package/src/excel-loaders/members.ts +102 -103
  143. package/src/excel-loaders/payments.ts +155 -156
  144. package/src/helpers/AddressValidator.test.ts +32 -32
  145. package/src/helpers/AddressValidator.ts +128 -122
  146. package/src/helpers/AdminPermissionChecker.ts +339 -236
  147. package/src/helpers/AuthenticatedStructures.ts +233 -134
  148. package/src/helpers/BuckarooHelper.ts +134 -134
  149. package/src/helpers/CheckSettlements.ts +94 -88
  150. package/src/helpers/Context.ts +87 -86
  151. package/src/helpers/CookieHelper.ts +23 -22
  152. package/src/helpers/EmailResumer.ts +10 -10
  153. package/src/helpers/FileCache.ts +62 -62
  154. package/src/helpers/ForwardHandler.test.ts +122 -124
  155. package/src/helpers/ForwardHandler.ts +76 -70
  156. package/src/helpers/MemberUserSyncer.ts +101 -96
  157. package/src/helpers/MembershipCharger.ts +69 -69
  158. package/src/helpers/MembershipHelper.ts +11 -12
  159. package/src/helpers/OpenIDConnectHelper.ts +85 -82
  160. package/src/helpers/PeriodHelper.ts +65 -70
  161. package/src/helpers/StripeHelper.ts +146 -137
  162. package/src/helpers/StripePayoutChecker.ts +51 -52
  163. package/src/helpers/ViesHelper.ts +46 -44
  164. package/src/helpers/fetchToAsyncIterator.ts +14 -14
  165. package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
  166. package/src/middleware/ContextMiddleware.ts +5 -5
  167. package/src/migrations/1646578856-validate-addresses.ts +6 -9
  168. package/src/seeds/0000000000-example.ts +3 -5
  169. package/src/seeds/1715028563-user-permissions.ts +16 -18
  170. package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
  171. package/src/seeds/1722344162-sync-member-users.ts +14 -15
  172. package/src/seeds/1722344162-update-membership.ts +6 -6
  173. package/src/seeds/1726055544-balance-item-paid.ts +4 -4
  174. package/src/seeds/1726055545-balance-item-pending.ts +4 -4
  175. package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
  176. package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
  177. package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
  178. package/src/seeds/1726847064-setup-steps.ts +16 -0
  179. package/src/sql-filters/balance-item-payments.ts +7 -7
  180. package/src/sql-filters/events.ts +14 -14
  181. package/src/sql-filters/members.ts +96 -96
  182. package/src/sql-filters/organizations.ts +139 -75
  183. package/src/sql-filters/payments.ts +28 -28
  184. package/src/sql-filters/registrations.ts +14 -14
  185. package/src/sql-sorters/events.ts +25 -25
  186. package/src/sql-sorters/members.ts +26 -26
  187. package/src/sql-sorters/organizations.ts +36 -36
  188. package/src/sql-sorters/payments.ts +26 -26
  189. package/tests/e2e/stock.test.ts +616 -621
  190. package/tests/e2e/tickets.test.ts +255 -260
  191. package/tests/helpers/StripeMocker.ts +177 -179
  192. package/tests/helpers/TestServer.ts +9 -9
  193. package/tests/jest.global.setup.ts +14 -13
  194. package/tests/jest.setup.ts +33 -32
  195. package/.eslintrc.js +0 -61
  196. package/jest.config.js +0 -11
  197. package/src/helpers/SetupStepsUpdater.ts +0 -359
  198. package/src/seeds/1724076679-setup-steps.ts +0 -16
@@ -1,46 +1,46 @@
1
1
  import { Decoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { I18n } from '@stamhoofd/backend-i18n';
5
5
  import { Email } from '@stamhoofd/email';
6
- import { getEmailBuilder,RateLimiter } from '@stamhoofd/models';
7
- import { EmailRequest, Recipient } from "@stamhoofd/structures";
6
+ import { getEmailBuilder, RateLimiter } from '@stamhoofd/models';
7
+ import { EmailRequest, Recipient } from '@stamhoofd/structures';
8
8
 
9
9
  import { Context } from '../../../../helpers/Context';
10
10
 
11
11
  type Params = Record<string, never>;
12
12
  type Query = undefined;
13
- type Body = EmailRequest
13
+ type Body = EmailRequest;
14
14
  type ResponseBody = undefined;
15
15
 
16
16
  export const paidEmailRateLimiter = new RateLimiter({
17
17
  limits: [
18
- {
18
+ {
19
19
  // Max 5.000 emails a day
20
20
  limit: 5000,
21
- duration: 24 * 60 * 1000 * 60
21
+ duration: 24 * 60 * 1000 * 60,
22
22
  },
23
- {
23
+ {
24
24
  // 10.000 requests per week
25
25
  limit: 10000,
26
- duration: 24 * 60 * 1000 * 60 * 7
27
- }
28
- ]
26
+ duration: 24 * 60 * 1000 * 60 * 7,
27
+ },
28
+ ],
29
29
  });
30
30
 
31
31
  export const freeEmailRateLimiter = new RateLimiter({
32
32
  limits: [
33
- {
33
+ {
34
34
  // Max 100 a day
35
35
  limit: 100,
36
- duration: 24 * 60 * 1000 * 60
36
+ duration: 24 * 60 * 1000 * 60,
37
37
  },
38
- {
38
+ {
39
39
  // Max 200 a week
40
40
  limit: 200,
41
- duration: 7 * 24 * 60 * 1000 * 60
42
- }
43
- ]
41
+ duration: 7 * 24 * 60 * 1000 * 60,
42
+ },
43
+ ],
44
44
  });
45
45
 
46
46
  /**
@@ -48,14 +48,14 @@ export const freeEmailRateLimiter = new RateLimiter({
48
48
  */
49
49
 
50
50
  export class EmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
51
- bodyDecoder = EmailRequest as Decoder<EmailRequest>
51
+ bodyDecoder = EmailRequest as Decoder<EmailRequest>;
52
52
 
53
53
  protected doesMatch(request: Request): [true, Params] | [false] {
54
- if (request.method != "POST") {
54
+ if (request.method !== 'POST') {
55
55
  return [false];
56
56
  }
57
57
 
58
- const params = Endpoint.parseParameters(request.url, "/email/legacy", {});
58
+ const params = Endpoint.parseParameters(request.url, '/email/legacy', {});
59
59
 
60
60
  if (params) {
61
61
  return [true, params as Params];
@@ -65,152 +65,153 @@ export class EmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
65
65
 
66
66
  async handle(request: DecodedRequest<Params, Query, Body>) {
67
67
  const organization = await Context.setOrganizationScope();
68
- const {user} = await Context.authenticate()
68
+ const { user } = await Context.authenticate();
69
69
 
70
70
  if (!Context.auth.canSendEmails()) {
71
- throw Context.auth.error()
72
- }
71
+ throw Context.auth.error();
72
+ }
73
73
 
74
74
  if (request.body.recipients.length > 5000) {
75
75
  throw new SimpleError({
76
- code: "too_many_recipients",
77
- message: "Too many recipients",
78
- human: "Je kan maar een mail naar maximaal 5000 personen tergelijk versturen. Contacteer ons om deze limiet te verhogen indien dit nodig is.",
79
- field: "recipients"
80
- })
76
+ code: 'too_many_recipients',
77
+ message: 'Too many recipients',
78
+ human: 'Je kan maar een mail naar maximaal 5000 personen tergelijk versturen. Contacteer ons om deze limiet te verhogen indien dit nodig is.',
79
+ field: 'recipients',
80
+ });
81
81
  }
82
82
 
83
83
  // For non paid organizations, the limit is 10
84
84
  if (request.body.recipients.length > 10 && !organization.meta.packages.isPaid) {
85
85
  throw new SimpleError({
86
- code: "too_many_emails",
87
- message: "Too many e-mails",
88
- human: "Zolang je de demo versie van Stamhoofd gebruikt kan je maar maximaal een email sturen naar 10 emailadressen. Als je het pakket aankoopt zal deze limiet er niet zijn. Dit is om misbruik te voorkomen met spammers die spam email versturen via Stamhoofd.",
89
- field: "recipients"
90
- })
86
+ code: 'too_many_emails',
87
+ message: 'Too many e-mails',
88
+ human: 'Zolang je de demo versie van Stamhoofd gebruikt kan je maar maximaal een email sturen naar 10 emailadressen. Als je het pakket aankoopt zal deze limiet er niet zijn. Dit is om misbruik te voorkomen met spammers die spam email versturen via Stamhoofd.',
89
+ field: 'recipients',
90
+ });
91
91
  }
92
92
 
93
- const limiter = organization.meta.packages.isPaid ? paidEmailRateLimiter : freeEmailRateLimiter
93
+ const limiter = organization.meta.packages.isPaid ? paidEmailRateLimiter : freeEmailRateLimiter;
94
94
 
95
95
  try {
96
96
  limiter.track(organization.id, request.body.recipients.length);
97
- } catch (e) {
97
+ }
98
+ catch (e) {
98
99
  Email.sendWebmaster({
99
- subject: "[Limiet] Limiet bereikt voor aantal e-mails",
100
- text: "Beste, \nDe limiet werd bereikt voor het aantal e-mails per dag. \nVereniging: "+organization.id+" ("+organization.name+")" + "\n\n" + e.message + "\n\nStamhoofd"
101
- })
100
+ subject: '[Limiet] Limiet bereikt voor aantal e-mails',
101
+ text: 'Beste, \nDe limiet werd bereikt voor het aantal e-mails per dag. \nVereniging: ' + organization.id + ' (' + organization.name + ')' + '\n\n' + e.message + '\n\nStamhoofd',
102
+ });
102
103
 
103
104
  throw new SimpleError({
104
- code: "too_many_emails_period",
105
- message: "Too many e-mails limited",
106
- human: "Oeps! Om spam te voorkomen limiteren we het aantal emails die je per dag/week kan versturen. Neem contact met ons op om deze limiet te verhogen.",
107
- field: "recipients"
108
- })
105
+ code: 'too_many_emails_period',
106
+ message: 'Too many e-mails limited',
107
+ human: 'Oeps! Om spam te voorkomen limiteren we het aantal emails die je per dag/week kan versturen. Neem contact met ons op om deze limiet te verhogen.',
108
+ field: 'recipients',
109
+ });
109
110
  }
110
-
111
111
 
112
112
  // Validate email
113
- const sender = organization.privateMeta.emails.find(e => e.id == request.body.emailId)
113
+ const sender = organization.privateMeta.emails.find(e => e.id == request.body.emailId);
114
114
  if (!sender) {
115
115
  throw new SimpleError({
116
- code: "invalid_field",
117
- message: "Invalid emailId",
118
- human: "Het e-mailadres waarvan je wilt versturen bestaat niet (meer). Kijk je het na?",
119
- field: "emailId"
120
- })
116
+ code: 'invalid_field',
117
+ message: 'Invalid emailId',
118
+ human: 'Het e-mailadres waarvan je wilt versturen bestaat niet (meer). Kijk je het na?',
119
+ field: 'emailId',
120
+ });
121
121
  }
122
122
 
123
123
  // Validate attachments
124
124
  const size = request.body.attachments.reduce((value: number, attachment) => {
125
- return value + attachment.content.length
126
- }, 0)
127
-
128
- if (size > 9.5*1024*1024) {
125
+ return value + attachment.content.length;
126
+ }, 0);
127
+
128
+ if (size > 9.5 * 1024 * 1024) {
129
129
  throw new SimpleError({
130
- code: "too_big_attachments",
131
- message: "Too big attachments",
132
- human: "Jouw bericht is te groot. Grote bijlages verstuur je beter niet via e-mail, je plaatst dan best een link naar de locatie in bv. Google Drive. De maximale grootte van een e-mail is 10MB, inclusief het bericht. Als je grote bestanden verstuurt kan je ze ook proberen te verkleinen.",
133
- field: "attachments"
134
- })
130
+ code: 'too_big_attachments',
131
+ message: 'Too big attachments',
132
+ human: 'Jouw bericht is te groot. Grote bijlages verstuur je beter niet via e-mail, je plaatst dan best een link naar de locatie in bv. Google Drive. De maximale grootte van een e-mail is 10MB, inclusief het bericht. Als je grote bestanden verstuurt kan je ze ook proberen te verkleinen.',
133
+ field: 'attachments',
134
+ });
135
135
  }
136
136
 
137
137
  const safeContentTypes = [
138
- "application/msword",
139
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
140
- "application/vnd.ms-excel",
141
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
142
- "application/pdf",
143
- "image/jpeg",
144
- "image/png",
145
- "image/gif"
146
- ]
138
+ 'application/msword',
139
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
140
+ 'application/vnd.ms-excel',
141
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
142
+ 'application/pdf',
143
+ 'image/jpeg',
144
+ 'image/png',
145
+ 'image/gif',
146
+ ];
147
147
 
148
148
  for (const attachment of request.body.attachments) {
149
149
  if (attachment.contentType && !safeContentTypes.includes(attachment.contentType)) {
150
150
  throw new SimpleError({
151
- code: "content_type_not_supported",
152
- message: "Content-Type not supported",
153
- human: "Het bestandstype van jouw bijlage wordt niet ondersteund of is onveilig om in een e-mail te plaatsen. Overweeg om je bestand op bv. Google Drive te zetten en de link in jouw e-mail te zetten.",
154
- field: "attachments"
155
- })
151
+ code: 'content_type_not_supported',
152
+ message: 'Content-Type not supported',
153
+ human: 'Het bestandstype van jouw bijlage wordt niet ondersteund of is onveilig om in een e-mail te plaatsen. Overweeg om je bestand op bv. Google Drive te zetten en de link in jouw e-mail te zetten.',
154
+ field: 'attachments',
155
+ });
156
156
  }
157
157
  }
158
158
 
159
159
  const attachments = request.body.attachments.map((attachment, index) => {
160
- let filename = "bijlage-"+index;
161
-
162
- if (attachment.contentType == "application/pdf") {
160
+ let filename = 'bijlage-' + index;
161
+
162
+ if (attachment.contentType == 'application/pdf') {
163
163
  // tmp solution for pdf only
164
- filename += ".pdf"
164
+ filename += '.pdf';
165
165
  }
166
166
 
167
167
  // Correct file name if needed
168
168
  if (attachment.filename) {
169
- filename = attachment.filename.toLowerCase().replace(/[^a-z0-9.]+/g, "-").replace(/^-+/, "").replace(/-+$/, "")
169
+ filename = attachment.filename.toLowerCase().replace(/[^a-z0-9.]+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
170
170
  }
171
171
 
172
172
  return {
173
173
  filename: filename,
174
174
  content: attachment.content,
175
175
  contentType: attachment.contentType ?? undefined,
176
- encoding: "base64"
177
- }
178
- })
176
+ encoding: 'base64',
177
+ };
178
+ });
179
179
 
180
180
  let from = organization.getDefaultFrom(request.i18n, false, 'broadcast');
181
181
  let replyTo: string | undefined = sender.email;
182
182
 
183
183
  // Can we send from this e-mail or reply-to?
184
- if (organization.privateMeta.mailDomain && organization.privateMeta.mailDomainActive && sender.email.endsWith("@"+organization.privateMeta.mailDomain)) {
185
- from = sender.email
184
+ if (organization.privateMeta.mailDomain && organization.privateMeta.mailDomainActive && sender.email.endsWith('@' + organization.privateMeta.mailDomain)) {
185
+ from = sender.email;
186
186
  replyTo = undefined;
187
187
  }
188
188
 
189
189
  // Include name in form field
190
190
  if (sender.name) {
191
- from = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
192
- } else {
193
- from = '"'+organization.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
191
+ from = '"' + sender.name.replaceAll('"', '\\"') + '" <' + from + '>';
192
+ }
193
+ else {
194
+ from = '"' + organization.name.replaceAll('"', '\\"') + '" <' + from + '>';
194
195
  }
195
196
 
196
- const email = request.body
197
+ const email = request.body;
197
198
 
198
199
  if (!email.html) {
199
200
  throw new SimpleError({
200
- code: "missing_field",
201
- message: "Missing html",
202
- human: "Je hebt geen inhoud ingevuld voor je e-mail. Vul een bericht in en probeer opnieuw.",
203
- field: "html"
204
- })
201
+ code: 'missing_field',
202
+ message: 'Missing html',
203
+ human: 'Je hebt geen inhoud ingevuld voor je e-mail. Vul een bericht in en probeer opnieuw.',
204
+ field: 'html',
205
+ });
205
206
  }
206
207
 
207
208
  if (!email.subject) {
208
209
  throw new SimpleError({
209
- code: "missing_field",
210
- message: "Missing subject",
211
- human: "Je hebt geen onderwerp ingevuld voor je e-mail. Vul een onderwerp in en probeer opnieuw.",
212
- field: "subject"
213
- })
210
+ code: 'missing_field',
211
+ message: 'Missing subject',
212
+ human: 'Je hebt geen onderwerp ingevuld voor je e-mail. Vul een onderwerp in en probeer opnieuw.',
213
+ field: 'subject',
214
+ });
214
215
  }
215
216
 
216
217
  // Create e-mail builder
@@ -221,32 +222,32 @@ export class EmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
221
222
  from,
222
223
  replyTo,
223
224
  attachments,
224
- defaultReplacements: request.body.defaultReplacements ?? []
225
- })
225
+ defaultReplacements: request.body.defaultReplacements ?? [],
226
+ });
226
227
 
227
- Email.schedule(builder)
228
+ Email.schedule(builder);
228
229
 
229
230
  // Also send a copy
230
- const recipient = Recipient.create(email.recipients[0])
231
- recipient.email = sender.email
232
- recipient.firstName = sender.name ?? null
233
- recipient.lastName = null
234
- recipient.userId = null
235
-
236
- const prefix = "<p><i>Kopie e-mail verzonden door "+user.firstName+" "+user.lastName+"</i><br /><br /></p>"
231
+ const recipient = Recipient.create(email.recipients[0]);
232
+ recipient.email = sender.email;
233
+ recipient.firstName = sender.name ?? null;
234
+ recipient.lastName = null;
235
+ recipient.userId = null;
236
+
237
+ const prefix = '<p><i>Kopie e-mail verzonden door ' + user.firstName + ' ' + user.lastName + '</i><br /><br /></p>';
237
238
  const builder2 = await getEmailBuilder(organization, {
238
239
  ...email,
239
- subject: "[KOPIE] "+email.subject,
240
- html: email.html.replace("<body>", "<body>"+prefix),
240
+ subject: '[KOPIE] ' + email.subject,
241
+ html: email.html.replace('<body>', '<body>' + prefix),
241
242
  recipients: [
242
- recipient
243
+ recipient,
243
244
  ],
244
245
  from,
245
246
  replyTo,
246
- attachments
247
- })
247
+ attachments,
248
+ });
248
249
 
249
- Email.schedule(builder2)
250
+ Email.schedule(builder2);
250
251
 
251
252
  return new Response(undefined);
252
253
  }
@@ -1,5 +1,5 @@
1
1
  import { AutoEncoder, Data, Decoder, EnumDecoder, field, StringDecoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { EmailTemplate } from '@stamhoofd/models';
4
4
  import { EmailTemplate as EmailTemplateStruct, EmailTemplateType } from '@stamhoofd/structures';
5
5
 
@@ -12,13 +12,13 @@ type Body = undefined;
12
12
 
13
13
  class Query extends AutoEncoder {
14
14
  @field({ decoder: new StringNullableDecoder(StringDecoder), optional: true, nullable: true })
15
- webshopId: string|null = null
15
+ webshopId: string | null = null;
16
16
 
17
- @field({ decoder: new StringNullableDecoder(new StringArrayDecoder(StringDecoder)), optional: true, nullable: true})
18
- groupIds: string[]|null = null
17
+ @field({ decoder: new StringNullableDecoder(new StringArrayDecoder(StringDecoder)), optional: true, nullable: true })
18
+ groupIds: string[] | null = null;
19
19
 
20
- @field({ decoder: new StringNullableDecoder(new StringArrayDecoder(new EnumDecoder(EmailTemplateType))), optional: true, nullable: true})
21
- types: EmailTemplateType[]|null = null
20
+ @field({ decoder: new StringNullableDecoder(new StringArrayDecoder(new EnumDecoder(EmailTemplateType))), optional: true, nullable: true })
21
+ types: EmailTemplateType[] | null = null;
22
22
  }
23
23
 
24
24
  type ResponseBody = EmailTemplateStruct[];
@@ -27,11 +27,11 @@ export class GetEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, Res
27
27
  queryDecoder = Query as Decoder<Query>;
28
28
 
29
29
  protected doesMatch(request: Request): [true, Params] | [false] {
30
- if (request.method != "GET") {
30
+ if (request.method !== 'GET') {
31
31
  return [false];
32
32
  }
33
33
 
34
- const params = Endpoint.parseParameters(request.url, "/email-templates", {});
34
+ const params = Endpoint.parseParameters(request.url, '/email-templates', {});
35
35
 
36
36
  if (params) {
37
37
  return [true, params as Params];
@@ -41,37 +41,37 @@ export class GetEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, Res
41
41
 
42
42
  async handle(request: DecodedRequest<Params, Query, Body>) {
43
43
  const organization = await Context.setOptionalOrganizationScope();
44
- await Context.authenticate()
44
+ await Context.authenticate();
45
45
 
46
46
  if (organization) {
47
47
  if (!await Context.auth.canReadEmailTemplates(organization.id)) {
48
- throw Context.auth.error()
49
- }
50
- } else {
48
+ throw Context.auth.error();
49
+ }
50
+ }
51
+ else {
51
52
  if (!Context.auth.hasPlatformFullAccess()) {
52
- throw Context.auth.error()
53
- }
53
+ throw Context.auth.error();
54
+ }
54
55
  }
55
56
 
56
- const types = (request.query.types ?? [...Object.values(EmailTemplateType)]).filter(type => {
57
+ const types = (request.query.types ?? [...Object.values(EmailTemplateType)]).filter((type) => {
57
58
  if (!organization) {
58
- return EmailTemplateStruct.allowPlatformLevel(type)
59
+ return EmailTemplateStruct.allowPlatformLevel(type);
59
60
  }
60
- return EmailTemplateStruct.allowOrganizationLevel(type)
61
- })
61
+ return EmailTemplateStruct.allowOrganizationLevel(type);
62
+ });
62
63
 
63
-
64
- const templates = organization ?
65
- (
66
- await EmailTemplate.where({ organizationId: organization.id, webshopId: request.query.webshopId ?? null, groupId: request.query.groupIds ? {sign: 'IN', value: request.query.groupIds} : null, type: {sign: 'IN', value: types}})
67
- )
64
+ const templates = organization
65
+ ? (
66
+ await EmailTemplate.where({ organizationId: organization.id, webshopId: request.query.webshopId ?? null, groupId: request.query.groupIds ? { sign: 'IN', value: request.query.groupIds } : null, type: { sign: 'IN', value: types } })
67
+ )
68
68
  : (
69
69
  // Required for event emails when logged in as the platform admin
70
- (request.query.webshopId || request.query.groupIds) ?
71
- await EmailTemplate.where({ webshopId: request.query.webshopId ?? null, groupId: request.query.groupIds ? {sign: 'IN', value: request.query.groupIds} : null, type: {sign: 'IN', value: types}})
72
- : []
73
- );
74
- const defaultTemplates = await EmailTemplate.where({ organizationId: null, type: {sign: 'IN', value: types} });
75
- return new Response([...templates, ...defaultTemplates].map(template => EmailTemplateStruct.create(template)))
70
+ (request.query.webshopId || request.query.groupIds)
71
+ ? await EmailTemplate.where({ webshopId: request.query.webshopId ?? null, groupId: request.query.groupIds ? { sign: 'IN', value: request.query.groupIds } : null, type: { sign: 'IN', value: types } })
72
+ : []
73
+ );
74
+ const defaultTemplates = await EmailTemplate.where({ organizationId: null, type: { sign: 'IN', value: types } });
75
+ return new Response([...templates, ...defaultTemplates].map(template => EmailTemplateStruct.create(template)));
76
76
  }
77
77
  }
@@ -1,5 +1,5 @@
1
1
  import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
2
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { EmailTemplate, Group, Webshop } from '@stamhoofd/models';
4
4
  import { EmailTemplate as EmailTemplateStruct, PermissionLevel } from '@stamhoofd/structures';
5
5
 
@@ -13,14 +13,14 @@ type Query = undefined;
13
13
  type ResponseBody = EmailTemplateStruct[];
14
14
 
15
15
  export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
16
- bodyDecoder = new PatchableArrayDecoder(EmailTemplateStruct as Decoder<EmailTemplateStruct>, EmailTemplateStruct.patchType() as Decoder<AutoEncoderPatchType<EmailTemplateStruct>>, StringDecoder)
16
+ bodyDecoder = new PatchableArrayDecoder(EmailTemplateStruct as Decoder<EmailTemplateStruct>, EmailTemplateStruct.patchType() as Decoder<AutoEncoderPatchType<EmailTemplateStruct>>, StringDecoder);
17
17
 
18
18
  protected doesMatch(request: Request): [true, Params] | [false] {
19
- if (request.method != "PATCH") {
19
+ if (request.method !== 'PATCH') {
20
20
  return [false];
21
21
  }
22
22
 
23
- const params = Endpoint.parseParameters(request.url, "/email-templates", {});
23
+ const params = Endpoint.parseParameters(request.url, '/email-templates', {});
24
24
 
25
25
  if (params) {
26
26
  return [true, params as Params];
@@ -30,39 +30,40 @@ export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, R
30
30
 
31
31
  async handle(request: DecodedRequest<Params, Query, Body>) {
32
32
  const organization = await Context.setOptionalOrganizationScope();
33
- await Context.authenticate()
33
+ await Context.authenticate();
34
34
 
35
35
  if (organization) {
36
36
  if (!await Context.auth.canReadEmailTemplates(organization.id)) {
37
- throw Context.auth.error()
38
- }
39
- } else {
37
+ throw Context.auth.error();
38
+ }
39
+ }
40
+ else {
40
41
  if (!Context.auth.hasPlatformFullAccess()) {
41
- throw Context.auth.error()
42
- }
42
+ throw Context.auth.error();
43
+ }
43
44
  }
44
45
 
45
- const templates: EmailTemplate[] = []
46
+ const templates: EmailTemplate[] = [];
46
47
 
47
48
  // Get all patches
48
49
  for (const patch of request.body.getPatches()) {
49
- const template = await EmailTemplate.getByID(patch.id)
50
+ const template = await EmailTemplate.getByID(patch.id);
50
51
  if (!template || !(await Context.auth.canAccessEmailTemplate(template, PermissionLevel.Write))) {
51
- throw Context.auth.notFoundOrNoAccess("Je hebt geen toegang om deze emailtemplate te bewerken")
52
- }
53
-
54
- template.html = patch.html ?? template.html
55
- template.subject = patch.subject ?? template.subject
56
- template.text = patch.text ?? template.text
57
- template.json = patch.json ?? template.json
52
+ throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang om deze emailtemplate te bewerken');
53
+ }
58
54
 
59
- await template.save()
55
+ template.html = patch.html ?? template.html;
56
+ template.subject = patch.subject ?? template.subject;
57
+ template.text = patch.text ?? template.text;
58
+ template.json = patch.json ?? template.json;
60
59
 
61
- templates.push(template)
60
+ await template.save();
61
+
62
+ templates.push(template);
62
63
  }
63
64
 
64
65
  for (const put of request.body.getPuts()) {
65
- const struct = put.put
66
+ const struct = put.put;
66
67
 
67
68
  if (!EmailTemplateStruct.allowOrganizationLevel(struct.type) && organization) {
68
69
  throw Context.auth.error();
@@ -72,54 +73,54 @@ export class PatchEmailTemplatesEndpoint extends Endpoint<Params, Query, Body, R
72
73
  throw Context.auth.error();
73
74
  }
74
75
 
75
- const template = new EmailTemplate()
76
- template.id = struct.id
77
- template.organizationId = organization?.id ?? null
78
- template.webshopId = struct.webshopId
79
- template.groupId = struct.groupId
76
+ const template = new EmailTemplate();
77
+ template.id = struct.id;
78
+ template.organizationId = organization?.id ?? null;
79
+ template.webshopId = struct.webshopId;
80
+ template.groupId = struct.groupId;
80
81
 
81
82
  if (struct.groupId) {
82
- const group = await Group.getByID(struct.groupId)
83
+ const group = await Group.getByID(struct.groupId);
83
84
  if (!group || !await Context.auth.canAccessGroup(group, PermissionLevel.Full)) {
84
85
  throw Context.auth.error();
85
86
  }
86
- template.organizationId = group.organizationId
87
+ template.organizationId = group.organizationId;
87
88
  }
88
89
 
89
90
  if (struct.webshopId) {
90
- const webshop = await Webshop.getByID(struct.webshopId)
91
+ const webshop = await Webshop.getByID(struct.webshopId);
91
92
  if (!webshop || !await Context.auth.canAccessWebshop(webshop, PermissionLevel.Full)) {
92
93
  throw Context.auth.error();
93
94
  }
94
- template.organizationId = webshop.organizationId
95
+ template.organizationId = webshop.organizationId;
95
96
  }
96
97
 
97
- template.html = struct.html
98
- template.subject = struct.subject
99
- template.text = struct.text
100
- template.json = struct.json
98
+ template.html = struct.html;
99
+ template.subject = struct.subject;
100
+ template.text = struct.text;
101
+ template.json = struct.json;
101
102
 
102
- template.type = struct.type
103
+ template.type = struct.type;
103
104
 
104
105
  // Check if valid + write permissions
105
106
  if (!(await Context.auth.canAccessEmailTemplate(template, PermissionLevel.Write))) {
106
- throw Context.auth.error("Je hebt geen toegang om deze emailtemplate te maken")
107
- }
107
+ throw Context.auth.error('Je hebt geen toegang om deze emailtemplate te maken');
108
+ }
108
109
 
109
- await template.save()
110
+ await template.save();
110
111
 
111
- templates.push(template)
112
+ templates.push(template);
112
113
  }
113
114
 
114
115
  for (const id of request.body.getDeletes()) {
115
- const template = await EmailTemplate.getByID(id)
116
+ const template = await EmailTemplate.getByID(id);
116
117
  if (!template || !(await Context.auth.canAccessEmailTemplate(template, PermissionLevel.Write))) {
117
- throw Context.auth.notFoundOrNoAccess("Je hebt geen toegang om deze emailtemplate te verwijderen")
118
- }
119
-
120
- await template.delete()
118
+ throw Context.auth.notFoundOrNoAccess('Je hebt geen toegang om deze emailtemplate te verwijderen');
119
+ }
120
+
121
+ await template.delete();
121
122
  }
122
-
123
- return new Response(templates.map(template => EmailTemplateStruct.create(template)))
123
+
124
+ return new Response(templates.map(template => EmailTemplateStruct.create(template)));
124
125
  }
125
126
  }