@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,12 +1,12 @@
1
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
1
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
2
  import { Document, DocumentTemplate, Member } from '@stamhoofd/models';
3
- import { Document as DocumentStruct,DocumentStatus } from "@stamhoofd/structures";
4
- import { Sorter } from "@stamhoofd/utility";
3
+ import { Document as DocumentStruct, DocumentStatus } from '@stamhoofd/structures';
4
+ import { Sorter } from '@stamhoofd/utility';
5
5
 
6
- import { Context } from "../../../helpers/Context";
6
+ import { Context } from '../../../helpers/Context';
7
7
  type Params = Record<string, never>;
8
8
  type Query = undefined;
9
- type Body = undefined
9
+ type Body = undefined;
10
10
  type ResponseBody = DocumentStruct[];
11
11
 
12
12
  /**
@@ -14,11 +14,11 @@ type ResponseBody = DocumentStruct[];
14
14
  */
15
15
  export class GetUserMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
16
16
  protected doesMatch(request: Request): [true, Params] | [false] {
17
- if (request.method != "GET") {
17
+ if (request.method !== 'GET') {
18
18
  return [false];
19
19
  }
20
20
 
21
- const params = Endpoint.parseParameters(request.url, "/documents", {});
21
+ const params = Endpoint.parseParameters(request.url, '/documents', {});
22
22
 
23
23
  if (params) {
24
24
  return [true, params as Params];
@@ -29,52 +29,52 @@ export class GetUserMembersEndpoint extends Endpoint<Params, Query, Body, Respon
29
29
 
30
30
  async handle(_: DecodedRequest<Params, Query, Body>) {
31
31
  const organization = await Context.setUserOrganizationScope();
32
- const {user} = await Context.authenticate()
32
+ const { user } = await Context.authenticate();
33
33
 
34
- const members = await Member.getMembersWithRegistrationForUser(user)
35
- let templates = organization ? await DocumentTemplate.where({ status: 'Published', organizationId: organization.id }) : null
36
- const memberIds = members.map(m => m.id)
37
- const templateIds = templates ? templates.map(t => t.id) : null
34
+ const members = await Member.getMembersWithRegistrationForUser(user);
35
+ let templates = organization ? await DocumentTemplate.where({ status: 'Published', organizationId: organization.id }) : null;
36
+ const memberIds = members.map(m => m.id);
37
+ const templateIds = templates ? templates.map(t => t.id) : null;
38
38
 
39
39
  if (memberIds.length == 0 || (templateIds !== null && templateIds.length == 0)) {
40
40
  return new Response([]);
41
41
  }
42
42
 
43
- const w: any = {
43
+ const w: any = {
44
44
  memberId: {
45
45
  sign: 'IN',
46
- value: memberIds
46
+ value: memberIds,
47
47
  },
48
48
  status: {
49
49
  sign: 'IN',
50
- value: [DocumentStatus.MissingData, DocumentStatus.Published]
50
+ value: [DocumentStatus.MissingData, DocumentStatus.Published],
51
51
  },
52
- }
52
+ };
53
53
 
54
54
  if (templateIds !== null) {
55
55
  w.templateId = {
56
56
  sign: 'IN',
57
- value: templateIds
58
- }
57
+ value: templateIds,
58
+ };
59
59
  }
60
60
 
61
61
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
62
62
  const documents = await Document.where(w);
63
63
 
64
64
  if (!templates) {
65
- templates = documents.length > 0 ? await DocumentTemplate.getByIDs(...documents.map(d => d.templateId)) : []
65
+ templates = documents.length > 0 ? await DocumentTemplate.getByIDs(...documents.map(d => d.templateId)) : [];
66
66
  }
67
-
68
- const filteredDocuments = documents.filter(document => {
69
- const template = templates.find(t => t.id == document.templateId)
67
+
68
+ const filteredDocuments = documents.filter((document) => {
69
+ const template = templates.find(t => t.id == document.templateId);
70
70
  if (!template || (!template.updatesEnabled && document.status === DocumentStatus.MissingData)) {
71
- return false
71
+ return false;
72
72
  }
73
73
  return true;
74
- })
74
+ });
75
+
76
+ filteredDocuments.sort((a, b) => Sorter.byDateValue(a.createdAt, b.createdAt));
75
77
 
76
- filteredDocuments.sort((a, b) => Sorter.byDateValue(a.createdAt, b.createdAt))
77
-
78
78
  return new Response(filteredDocuments.map(d => d.getStructure()));
79
79
  }
80
80
  }
@@ -1,25 +1,25 @@
1
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
1
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
2
  import { Member } from '@stamhoofd/models';
3
- import { MembersBlob } from "@stamhoofd/structures";
3
+ import { MembersBlob } from '@stamhoofd/structures';
4
4
 
5
- import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
6
- import { Context } from "../../../helpers/Context";
5
+ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
6
+ import { Context } from '../../../helpers/Context';
7
7
 
8
8
  type Params = Record<string, never>;
9
9
  type Query = undefined;
10
- type Body = undefined
11
- type ResponseBody = MembersBlob
10
+ type Body = undefined;
11
+ type ResponseBody = MembersBlob;
12
12
 
13
13
  /**
14
14
  * Get the members of the user
15
15
  */
16
16
  export class GetUserMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
17
17
  protected doesMatch(request: Request): [true, Params] | [false] {
18
- if (request.method != "GET") {
18
+ if (request.method !== 'GET') {
19
19
  return [false];
20
20
  }
21
21
 
22
- const params = Endpoint.parseParameters(request.url, "/user/members", {});
22
+ const params = Endpoint.parseParameters(request.url, '/user/members', {});
23
23
 
24
24
  if (params) {
25
25
  return [true, params as Params];
@@ -30,12 +30,12 @@ export class GetUserMembersEndpoint extends Endpoint<Params, Query, Body, Respon
30
30
 
31
31
  async handle(_: DecodedRequest<Params, Query, Body>) {
32
32
  await Context.setUserOrganizationScope();
33
- const {user} = await Context.authenticate()
33
+ const { user } = await Context.authenticate();
34
+
35
+ const members = await Member.getMembersWithRegistrationForUser(user);
34
36
 
35
- const members = await Member.getMembersWithRegistrationForUser(user)
36
-
37
37
  return new Response(
38
- await AuthenticatedStructures.membersBlob(members)
38
+ await AuthenticatedStructures.membersBlob(members),
39
39
  );
40
40
  }
41
41
  }
@@ -1,8 +1,8 @@
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 { SimpleError } from '@simonbackx/simple-errors';
4
4
  import { Document, Member, mergeTwoMembers, RateLimiter } from '@stamhoofd/models';
5
- import { MemberDetails, MembersBlob, MemberWithRegistrationsBlob } from "@stamhoofd/structures";
5
+ import { MemberDetails, MembersBlob, MemberWithRegistrationsBlob } from '@stamhoofd/structures';
6
6
 
7
7
  import { Email } from '@stamhoofd/email';
8
8
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
@@ -11,32 +11,31 @@ import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
11
11
  import { PatchOrganizationMembersEndpoint } from '../../global/members/PatchOrganizationMembersEndpoint';
12
12
  type Params = Record<string, never>;
13
13
  type Query = undefined;
14
- type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>
15
- type ResponseBody = MembersBlob
14
+ type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>;
15
+ type ResponseBody = MembersBlob;
16
16
 
17
17
  export const securityCodeLimiter = new RateLimiter({
18
18
  limits: [
19
- {
19
+ {
20
20
  // Max 10 a day
21
21
  limit: 10,
22
- duration: 24 * 60 * 1000 * 60
23
- }
24
- ]
22
+ duration: 24 * 60 * 1000 * 60,
23
+ },
24
+ ],
25
25
  });
26
26
 
27
-
28
27
  /**
29
28
  * Allow to add, patch and delete multiple members simultaneously, which is needed in order to sync relational data that is saved encrypted in multiple members (e.g. parents)
30
29
  */
31
30
  export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
32
- bodyDecoder = new PatchableArrayDecoder(MemberWithRegistrationsBlob as Decoder<MemberWithRegistrationsBlob>, MemberWithRegistrationsBlob.patchType() as Decoder<AutoEncoderPatchType<MemberWithRegistrationsBlob>>, StringDecoder)
31
+ bodyDecoder = new PatchableArrayDecoder(MemberWithRegistrationsBlob as Decoder<MemberWithRegistrationsBlob>, MemberWithRegistrationsBlob.patchType() as Decoder<AutoEncoderPatchType<MemberWithRegistrationsBlob>>, StringDecoder);
33
32
 
34
33
  protected doesMatch(request: Request): [true, Params] | [false] {
35
- if (request.method != "PATCH") {
34
+ if (request.method !== 'PATCH') {
36
35
  return [false];
37
36
  }
38
37
 
39
- const params = Endpoint.parseParameters(request.url, "/members", {});
38
+ const params = Endpoint.parseParameters(request.url, '/members', {});
40
39
 
41
40
  if (params) {
42
41
  return [true, params as Params];
@@ -46,73 +45,72 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
46
45
 
47
46
  async handle(request: DecodedRequest<Params, Query, Body>) {
48
47
  const organization = await Context.setUserOrganizationScope();
49
- const {user} = await Context.authenticate()
48
+ const { user } = await Context.authenticate();
50
49
 
51
50
  // Process changes
52
- const addedMembers: Member[] = []
51
+ const addedMembers: Member[] = [];
53
52
  for (const put of request.body.getPuts()) {
54
- const struct = put.put
53
+ const struct = put.put;
55
54
 
56
- const member = new Member()
57
- member.id = struct.id
58
- member.organizationId = organization?.id ?? null
55
+ const member = new Member();
56
+ member.id = struct.id;
57
+ member.organizationId = organization?.id ?? null;
59
58
 
60
- struct.details.cleanData()
61
- member.details = struct.details
59
+ struct.details.cleanData();
60
+ member.details = struct.details;
62
61
 
63
62
  this.throwIfInvalidDetails(member.details);
64
63
 
65
- const duplicate = await this.checkDuplicate(member, struct.details.securityCode)
64
+ const duplicate = await this.checkDuplicate(member, struct.details.securityCode);
66
65
  if (duplicate) {
67
- addedMembers.push(duplicate)
68
- continue
66
+ addedMembers.push(duplicate);
67
+ continue;
69
68
  }
70
69
 
71
- await member.save()
72
- addedMembers.push(member)
70
+ await member.save();
71
+ addedMembers.push(member);
73
72
  }
74
73
 
75
-
76
74
  // Modify members
77
- let members = await Member.getMembersWithRegistrationForUser(user)
75
+ let members = await Member.getMembersWithRegistrationForUser(user);
78
76
 
79
77
  for (let struct of request.body.getPatches()) {
80
- const member = members.find((m) => m.id == struct.id)
78
+ const member = members.find(m => m.id == struct.id);
81
79
  if (!member) {
82
80
  throw new SimpleError({
83
- code: "invalid_member",
81
+ code: 'invalid_member',
84
82
  message: "This member does not exist or you don't have permissions to modify this member",
85
- human: "Je probeert een lid aan te passen die niet (meer) bestaat. Er ging ergens iets mis."
86
- })
83
+ human: 'Je probeert een lid aan te passen die niet (meer) bestaat. Er ging ergens iets mis.',
84
+ });
87
85
  }
88
- const securityCode = struct.details?.securityCode // will get cleared after the filter
89
- struct = await Context.auth.filterMemberPatch(member, struct)
86
+ const securityCode = struct.details?.securityCode; // will get cleared after the filter
87
+ struct = await Context.auth.filterMemberPatch(member, struct);
90
88
 
91
89
  if (struct.details) {
92
90
  if (struct.details.isPut()) {
93
91
  throw new SimpleError({
94
- code: "not_allowed",
95
- message: "Cannot override details",
96
- human: "Er ging iets mis bij het aanpassen van de gegevens van dit lid. Probeer het later opnieuw en neem contact op als het probleem zich blijft voordoen.",
97
- field: "details"
98
- })
92
+ code: 'not_allowed',
93
+ message: 'Cannot override details',
94
+ human: 'Er ging iets mis bij het aanpassen van de gegevens van dit lid. Probeer het later opnieuw en neem contact op als het probleem zich blijft voordoen.',
95
+ field: 'details',
96
+ });
99
97
  }
100
98
 
101
- member.details.patchOrPut(struct.details)
99
+ member.details.patchOrPut(struct.details);
102
100
  member.details.cleanData();
103
101
  this.throwIfInvalidDetails(member.details);
104
102
  }
105
103
 
106
104
  if (!member.details) {
107
105
  throw new SimpleError({
108
- code: "invalid_data",
109
- message: "No details provided",
110
- human: "Opgelet! Je gebruikt een oudere versie van de inschrijvingspagina die niet langer wordt ondersteund. Herlaad de website grondig en wis je browser cache.",
111
- field: "details"
112
- })
106
+ code: 'invalid_data',
107
+ message: 'No details provided',
108
+ human: 'Opgelet! Je gebruikt een oudere versie van de inschrijvingspagina die niet langer wordt ondersteund. Herlaad de website grondig en wis je browser cache.',
109
+ field: 'details',
110
+ });
113
111
  }
114
112
 
115
- /*const duplicate = await this.checkDuplicate(member, securityCode)
113
+ /* const duplicate = await this.checkDuplicate(member, securityCode)
116
114
  if (duplicate) {
117
115
  // Remove the member from the list
118
116
  members.splice(members.findIndex(m => m.id === member.id), 1)
@@ -120,113 +118,115 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
120
118
  // Add new
121
119
  addedMembers.push(duplicate)
122
120
  continue
123
- }*/
121
+ } */
124
122
 
125
123
  await member.save();
126
- await MemberUserSyncer.onChangeMember(member)
124
+ await MemberUserSyncer.onChangeMember(member);
127
125
 
128
126
  // Update documents
129
- await Document.updateForMember(member.id)
127
+ await Document.updateForMember(member.id);
130
128
  }
131
129
 
132
130
  // Modify members
133
131
  if (addedMembers.length > 0) {
134
132
  // Give access to created members
135
- await Member.users.reverse("members").link(user, addedMembers)
133
+ await Member.users.reverse('members').link(user, addedMembers);
136
134
  }
137
135
 
138
- await PatchOrganizationMembersEndpoint.deleteMembers(request.body.getDeletes())
139
-
140
- members = await Member.getMembersWithRegistrationForUser(user)
136
+ await PatchOrganizationMembersEndpoint.deleteMembers(request.body.getDeletes());
137
+
138
+ members = await Member.getMembersWithRegistrationForUser(user);
141
139
 
142
140
  for (const member of addedMembers) {
143
141
  const updatedMember = members.find(m => m.id === member.id);
144
142
  if (updatedMember) {
145
143
  // Make sure we also give access to other parents
146
- await MemberUserSyncer.onChangeMember(updatedMember)
144
+ await MemberUserSyncer.onChangeMember(updatedMember);
147
145
 
148
146
  if (!updatedMember.users.find(u => u.id === user.id)) {
149
147
  // Also link the user to the member if the email address is missing in the details
150
- await MemberUserSyncer.linkUser(user.email, updatedMember, true)
148
+ await MemberUserSyncer.linkUser(user.email, updatedMember, true);
151
149
  }
152
150
 
153
- await Document.updateForMember(updatedMember.id)
151
+ await Document.updateForMember(updatedMember.id);
154
152
  }
155
153
  }
156
154
 
157
-
158
155
  return new Response(
159
- await AuthenticatedStructures.membersBlob(members)
156
+ await AuthenticatedStructures.membersBlob(members),
160
157
  );
161
158
  }
162
159
 
163
- async checkDuplicate(member: Member, securityCode: string|null|undefined) {
160
+ async checkDuplicate(member: Member, securityCode: string | null | undefined) {
164
161
  // Check for duplicates and prevent creating a duplicate member by a user
165
162
  const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member);
166
163
  if (duplicate) {
167
164
  if (await duplicate.isSafeToMergeDuplicateWithoutSecurityCode()) {
168
- console.log("Merging duplicate without security code: allowed for " + duplicate.id)
169
- } else if (securityCode) {
165
+ console.log('Merging duplicate without security code: allowed for ' + duplicate.id);
166
+ }
167
+ else if (securityCode) {
170
168
  try {
171
169
  securityCodeLimiter.track(member.details.name, 1);
172
- } catch (e) {
170
+ }
171
+ catch (e) {
173
172
  Email.sendWebmaster({
174
- subject: "[Limiet] Limiet bereikt voor aantal beveiligingscodes",
175
- text: "Beste, \nDe limiet werd bereikt voor het aantal beveiligingscodes per dag. \nNaam lid: "+member.details.name+" (ID: "+duplicate.id+")" + "\n\n" + e.message + "\n\nStamhoofd"
176
- })
177
-
173
+ subject: '[Limiet] Limiet bereikt voor aantal beveiligingscodes',
174
+ text: 'Beste, \nDe limiet werd bereikt voor het aantal beveiligingscodes per dag. \nNaam lid: ' + member.details.name + ' (ID: ' + duplicate.id + ')' + '\n\n' + e.message + '\n\nStamhoofd',
175
+ });
176
+
178
177
  throw new SimpleError({
179
- code: "too_many_tries",
180
- message: "Too many securityCodes limited",
181
- human: "Oeps! Om spam te voorkomen limiteren we het aantal beveiligingscodes die je kan proberen. Probeer morgen opnieuw.",
182
- field: "details.securityCode"
183
- })
178
+ code: 'too_many_tries',
179
+ message: 'Too many securityCodes limited',
180
+ human: 'Oeps! Om spam te voorkomen limiteren we het aantal beveiligingscodes die je kan proberen. Probeer morgen opnieuw.',
181
+ field: 'details.securityCode',
182
+ });
184
183
  }
185
184
 
186
185
  // Entered the security code, so we can link the user to the member
187
186
  if (STAMHOOFD.environment !== 'development') {
188
187
  if (!duplicate.details.securityCode || securityCode !== duplicate.details.securityCode) {
189
188
  throw new SimpleError({
190
- code: "invalid_field",
189
+ code: 'invalid_field',
191
190
  field: 'details.securityCode',
192
- message: "Invalid security code",
191
+ message: 'Invalid security code',
193
192
  human: Context.i18n.$t('49753d6a-7ca4-4145-8024-0be05a9ab063'),
194
- statusCode: 400
195
- })
193
+ statusCode: 400,
194
+ });
196
195
  }
197
196
  }
198
197
 
199
- console.log("Merging duplicate: security code is correct - for " + duplicate.id)
200
- } else {
198
+ console.log('Merging duplicate: security code is correct - for ' + duplicate.id);
199
+ }
200
+ else {
201
201
  throw new SimpleError({
202
- code: "known_member_missing_rights",
203
- message: "Creating known member without sufficient access rights",
202
+ code: 'known_member_missing_rights',
203
+ message: 'Creating known member without sufficient access rights',
204
204
  human: `${member.details.firstName} is al gekend in ons systeem, maar jouw e-mailadres niet. Om toegang te krijgen heb je de beveiligingscode nodig.`,
205
- statusCode: 400
206
- })
205
+ statusCode: 400,
206
+ });
207
207
  }
208
208
 
209
209
  // Merge data
210
210
  // NOTE: We use mergeTwoMembers instead of mergeMultipleMembers, because we should never safe 'member' , because that one does not exist in the database
211
- await mergeTwoMembers(duplicate, member)
212
- return duplicate
211
+ await mergeTwoMembers(duplicate, member);
212
+ return duplicate;
213
213
  }
214
214
  }
215
215
 
216
216
  private throwIfInvalidDetails(details: MemberDetails) {
217
- if(details.firstName.length < 2) {
217
+ if (details.firstName.length < 2) {
218
218
  throw new SimpleError({
219
- code: "invalid_field",
220
- message: "Voornaam is te kort",
221
- field: "firstName"
219
+ code: 'invalid_field',
220
+ message: 'Voornaam is te kort',
221
+ field: 'firstName',
222
222
  });
223
223
  }
224
224
 
225
- if(details.lastName.length < 2) {
225
+ if (details.lastName.length < 2) {
226
226
  throw new SimpleError({
227
- code: "invalid_field",
228
- message: "Achternaam is te kort",
229
- field: "lastName"
227
+ code: 'invalid_field',
228
+ message: 'Achternaam is te kort',
229
+ field: 'lastName',
230
230
  });
231
231
  }
232
232
  }