@stamhoofd/backend 2.39.1 → 2.40.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 (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 +275 -273
  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 +58 -60
  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,41 +1,41 @@
1
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
1
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
2
  import { Group, Organization } from '@stamhoofd/models';
3
- import { GroupsWithOrganizations } from "@stamhoofd/structures";
3
+ import { GroupsWithOrganizations } from '@stamhoofd/structures';
4
4
 
5
- import { AutoEncoder, Decoder, field, StringDecoder } from "@simonbackx/simple-encoding";
6
- import { Formatter } from "@stamhoofd/utility";
7
- import { StringArrayDecoder } from "../../../decoders/StringArrayDecoder";
8
- import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
9
- import { Context } from "../../../helpers/Context";
10
- import { SimpleError } from "@simonbackx/simple-errors";
5
+ import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
6
+ import { Formatter } from '@stamhoofd/utility';
7
+ import { StringArrayDecoder } from '../../../decoders/StringArrayDecoder';
8
+ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
9
+ import { Context } from '../../../helpers/Context';
10
+ import { SimpleError } from '@simonbackx/simple-errors';
11
11
  type Params = Record<string, never>;
12
12
 
13
13
  class Query extends AutoEncoder {
14
14
  @field({ decoder: new StringArrayDecoder(StringDecoder) })
15
- ids: string[]
15
+ ids: string[];
16
16
 
17
17
  /**
18
18
  * List of organizations the requester already knows and doesn't need to be included in the response
19
19
  */
20
20
  @field({ decoder: new StringArrayDecoder(StringDecoder), optional: true })
21
- excludeOrganizationIds: string[] = []
21
+ excludeOrganizationIds: string[] = [];
22
22
  }
23
23
 
24
- type Body = undefined
25
- type ResponseBody = GroupsWithOrganizations
24
+ type Body = undefined;
25
+ type ResponseBody = GroupsWithOrganizations;
26
26
 
27
27
  /**
28
28
  * Get the members of the user
29
29
  */
30
30
  export class GetGroupsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
31
- queryDecoder = Query as Decoder<Query>
31
+ queryDecoder = Query as Decoder<Query>;
32
32
 
33
33
  protected doesMatch(request: Request): [true, Params] | [false] {
34
- if (request.method != "GET") {
34
+ if (request.method !== 'GET') {
35
35
  return [false];
36
36
  }
37
37
 
38
- const params = Endpoint.parseParameters(request.url, "/groups", {});
38
+ const params = Endpoint.parseParameters(request.url, '/groups', {});
39
39
 
40
40
  if (params) {
41
41
  return [true, params as Params];
@@ -46,34 +46,34 @@ export class GetGroupsEndpoint extends Endpoint<Params, Query, Body, ResponseBod
46
46
 
47
47
  async handle(request: DecodedRequest<Params, Query, Body>) {
48
48
  await Context.setOptionalOrganizationScope();
49
- await Context.optionalAuthenticate()
49
+ await Context.optionalAuthenticate();
50
50
 
51
51
  if (request.query.ids.length === 0) {
52
52
  return new Response(
53
53
  GroupsWithOrganizations.create({
54
54
  groups: [],
55
- organizations: []
56
- })
55
+ organizations: [],
56
+ }),
57
57
  );
58
58
  }
59
59
 
60
60
  if (request.query.ids.length > 100) {
61
61
  throw new SimpleError({
62
- code: "too_many_ids",
63
- message: "You can't request more than 100 groups at once"
64
- })
62
+ code: 'too_many_ids',
63
+ message: "You can't request more than 100 groups at once",
64
+ });
65
65
  }
66
66
 
67
- const groups = await Group.getByIDs(...request.query.ids)
67
+ const groups = await Group.getByIDs(...request.query.ids);
68
68
  const organizationIds = Formatter.uniqueArray(groups.map(g => g.organizationId).filter(id => !request.query.excludeOrganizationIds.includes(id)));
69
69
 
70
70
  const organizations = organizationIds.length > 0 ? (await Organization.getByIDs(...organizationIds)) : [];
71
-
71
+
72
72
  return new Response(
73
73
  GroupsWithOrganizations.create({
74
74
  groups: await AuthenticatedStructures.groups(groups),
75
- organizations: await AuthenticatedStructures.organizations(organizations)
76
- })
75
+ organizations: await AuthenticatedStructures.organizations(organizations),
76
+ }),
77
77
  );
78
78
  }
79
79
  }
@@ -1,13 +1,13 @@
1
- import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
- import { Member, MemberWithRegistrations } from "@stamhoofd/models";
1
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
2
+ import { Member, MemberWithRegistrations } from '@stamhoofd/models';
3
3
 
4
- import { MembersBlob } from "@stamhoofd/structures";
5
- import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
6
- import { Context } from "../../../helpers/Context";
4
+ import { MembersBlob } from '@stamhoofd/structures';
5
+ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
6
+ import { Context } from '../../../helpers/Context';
7
7
  type Params = { id: string };
8
- type Query = undefined
9
- type Body = undefined
10
- type ResponseBody = MembersBlob
8
+ type Query = undefined;
9
+ type Body = undefined;
10
+ type ResponseBody = MembersBlob;
11
11
 
12
12
  /**
13
13
  * One endpoint to create, patch and delete groups. Usefull because on organization setup, we need to create multiple groups at once. Also, sometimes we need to link values and update multiple groups at once
@@ -15,11 +15,11 @@ type ResponseBody = MembersBlob
15
15
 
16
16
  export class GetMemberFamilyEndpoint 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, "/organization/members/@id/family", { id: String});
22
+ const params = Endpoint.parseParameters(request.url, '/organization/members/@id/family', { id: String });
23
23
 
24
24
  if (params) {
25
25
  return [true, params as Params];
@@ -29,24 +29,25 @@ export class GetMemberFamilyEndpoint extends Endpoint<Params, Query, Body, Respo
29
29
 
30
30
  async handle(request: DecodedRequest<Params, Query, Body>) {
31
31
  const organization = await Context.setOptionalOrganizationScope();
32
- await Context.authenticate()
32
+ await Context.authenticate();
33
33
 
34
34
  // Fast throw first (more in depth checking for patches later)
35
35
  if (organization) {
36
36
  if (!await Context.auth.hasSomeAccess(organization.id)) {
37
- throw Context.auth.error()
38
- }
39
- } else {
37
+ throw Context.auth.error();
38
+ }
39
+ }
40
+ else {
40
41
  if (!Context.auth.hasSomePlatformAccess()) {
41
- throw Context.auth.error()
42
+ throw Context.auth.error();
42
43
  }
43
44
  }
44
45
 
45
- const members = (await Member.getFamilyWithRegistrations(request.params.id))
46
+ const members = (await Member.getFamilyWithRegistrations(request.params.id));
46
47
 
47
- let foundMember = false
48
+ let foundMember = false;
48
49
 
49
- const validatedMembers: MemberWithRegistrations[] = []
50
+ const validatedMembers: MemberWithRegistrations[] = [];
50
51
 
51
52
  for (const member of members) {
52
53
  if (member.id === request.params.id) {
@@ -54,23 +55,23 @@ export class GetMemberFamilyEndpoint extends Endpoint<Params, Query, Body, Respo
54
55
 
55
56
  // Check access to this member (this will automatically give access to the family)
56
57
  if (!await Context.auth.canAccessMember(member)) {
57
- throw Context.auth.error("Je hebt geen toegang tot dit lid")
58
+ throw Context.auth.error('Je hebt geen toegang tot dit lid');
58
59
  }
59
- validatedMembers.push(member)
60
+ validatedMembers.push(member);
60
61
  continue;
61
62
  }
62
63
  if (await Context.auth.canAccessMember(member)) {
63
64
  // Remove from result
64
- validatedMembers.push(member)
65
+ validatedMembers.push(member);
65
66
  }
66
67
  }
67
68
 
68
69
  if (!foundMember) {
69
- throw Context.auth.error("Je hebt geen toegang tot dit lid")
70
+ throw Context.auth.error('Je hebt geen toegang tot dit lid');
70
71
  }
71
72
 
72
73
  return new Response(
73
- await AuthenticatedStructures.membersBlob(validatedMembers)
74
+ await AuthenticatedStructures.membersBlob(validatedMembers),
74
75
  );
75
76
  }
76
77
  }
@@ -11,14 +11,14 @@ type Body = undefined;
11
11
  type ResponseBody = CountResponse;
12
12
 
13
13
  export class GetMembersCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
- queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>
14
+ queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>;
15
15
 
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, "/members/count", {});
21
+ const params = Endpoint.parseParameters(request.url, '/members/count', {});
22
22
 
23
23
  if (params) {
24
24
  return [true, params as Params];
@@ -28,16 +28,16 @@ export class GetMembersCountEndpoint extends Endpoint<Params, Query, Body, Respo
28
28
 
29
29
  async handle(request: DecodedRequest<Params, Query, Body>) {
30
30
  await Context.setOptionalOrganizationScope();
31
- await Context.authenticate()
32
- const query = await GetMembersEndpoint.buildQuery(request.query)
33
-
31
+ await Context.authenticate();
32
+ const query = await GetMembersEndpoint.buildQuery(request.query);
33
+
34
34
  const count = await query
35
35
  .count();
36
36
 
37
37
  return new Response(
38
38
  CountResponse.create({
39
- count
40
- })
39
+ count,
40
+ }),
41
41
  );
42
42
  }
43
43
  }
@@ -1,13 +1,12 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
1
  import { Decoder } from '@simonbackx/simple-encoding';
3
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
3
  import { SimpleError } from '@simonbackx/simple-errors';
5
4
  import { Member, Platform } from '@stamhoofd/models';
6
- import { SQL, compileToSQLFilter, compileToSQLSorter } from "@stamhoofd/sql";
5
+ import { SQL, compileToSQLFilter, compileToSQLSorter } from '@stamhoofd/sql';
7
6
  import { CountFilteredRequest, Country, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
8
7
  import { DataValidator } from '@stamhoofd/utility';
9
8
 
10
- import parsePhoneNumber from "libphonenumber-js/max";
9
+ import parsePhoneNumber from 'libphonenumber-js/max';
11
10
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
12
11
  import { Context } from '../../../helpers/Context';
13
12
  import { memberFilterCompilers } from '../../../sql-filters/members';
@@ -16,20 +15,20 @@ import { memberSorters } from '../../../sql-sorters/members';
16
15
  type Params = Record<string, never>;
17
16
  type Query = LimitedFilteredRequest;
18
17
  type Body = undefined;
19
- type ResponseBody = PaginatedResponse<MembersBlob, LimitedFilteredRequest>
18
+ type ResponseBody = PaginatedResponse<MembersBlob, LimitedFilteredRequest>;
20
19
 
21
- const sorters = memberSorters
22
- const filterCompilers = memberFilterCompilers
20
+ const sorters = memberSorters;
21
+ const filterCompilers = memberFilterCompilers;
23
22
 
24
23
  export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
25
- queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>
24
+ queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>;
26
25
 
27
26
  protected doesMatch(request: Request): [true, Params] | [false] {
28
- if (request.method != "GET") {
27
+ if (request.method !== 'GET') {
29
28
  return [false];
30
29
  }
31
30
 
32
- const params = Endpoint.parseParameters(request.url, "/members", {});
31
+ const params = Endpoint.parseParameters(request.url, '/members', {});
33
32
 
34
33
  if (params) {
35
34
  return [true, params as Params];
@@ -37,18 +36,18 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
37
36
  return [false];
38
37
  }
39
38
 
40
- static async buildQuery(q: CountFilteredRequest|LimitedFilteredRequest) {
41
- const organization = Context.organization
42
- let scopeFilter: StamhoofdFilter|undefined = undefined;
39
+ static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest) {
40
+ const organization = Context.organization;
41
+ let scopeFilter: StamhoofdFilter | undefined = undefined;
43
42
 
44
43
  if (!organization && !Context.auth.canAccessAllPlatformMembers()) {
45
- const tags = Context.auth.getPlatformAccessibleOrganizationTags(PermissionLevel.Read)
46
- if (tags != 'all' && tags.length === 0) {
47
- throw Context.auth.error()
44
+ const tags = Context.auth.getPlatformAccessibleOrganizationTags(PermissionLevel.Read);
45
+ if (tags !== 'all' && tags.length === 0) {
46
+ throw Context.auth.error();
48
47
  }
49
-
48
+
50
49
  if (tags !== 'all') {
51
- const platform = await Platform.getShared()
50
+ const platform = await Platform.getShared();
52
51
 
53
52
  // Add organization scope filter
54
53
  scopeFilter = {
@@ -56,27 +55,27 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
56
55
  $elemMatch: {
57
56
  organization: {
58
57
  tags: {
59
- $in: tags
60
- }
58
+ $in: tags,
59
+ },
61
60
  },
62
61
  periodId: platform.periodId,
63
62
  group: {
64
63
  defaultAgeGroupId: {
65
- $neq: null
66
- }
67
- }
68
- }
69
- }
64
+ $neq: null,
65
+ },
66
+ },
67
+ },
68
+ },
70
69
  };
71
70
  }
72
71
  }
73
72
 
74
73
  if (organization) {
75
74
  // Add organization scope filter
76
- const groups = await Context.auth.getAccessibleGroups(organization.id)
75
+ const groups = await Context.auth.getAccessibleGroups(organization.id);
77
76
 
78
77
  if (groups.length === 0) {
79
- throw Context.auth.error()
78
+ throw Context.auth.error();
80
79
  }
81
80
 
82
81
  if (groups === 'all') {
@@ -86,173 +85,177 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
86
85
  registrations: {
87
86
  $elemMatch: {
88
87
  organizationId: organization.id,
89
- }
90
- }
88
+ },
89
+ },
91
90
  };
92
- } else {
91
+ }
92
+ else {
93
93
  // Can only access current period
94
94
  scopeFilter = {
95
95
  registrations: {
96
96
  $elemMatch: {
97
97
  organizationId: organization.id,
98
98
  periodId: organization.periodId,
99
- }
100
- }
99
+ },
100
+ },
101
101
  };
102
102
  }
103
- } else {
103
+ }
104
+ else {
104
105
  scopeFilter = {
105
106
  registrations: {
106
107
  $elemMatch: {
107
108
  organizationId: organization.id,
108
109
  periodId: organization.periodId,
109
110
  groupId: {
110
- $in: groups
111
- }
112
- }
113
- }
111
+ $in: groups,
112
+ },
113
+ },
114
+ },
114
115
  };
115
116
  }
116
117
  }
117
-
118
+
118
119
  const query = SQL
119
120
  .select(
120
- SQL.column('members', 'id')
121
+ SQL.column('members', 'id'),
121
122
  )
122
123
  .from(
123
- SQL.table('members')
124
+ SQL.table('members'),
124
125
  );
125
-
126
+
126
127
  if (scopeFilter) {
127
- query.where(compileToSQLFilter(scopeFilter, filterCompilers))
128
+ query.where(compileToSQLFilter(scopeFilter, filterCompilers));
128
129
  }
129
130
 
130
131
  if (q.filter) {
131
- query.where(compileToSQLFilter(q.filter, filterCompilers))
132
+ query.where(compileToSQLFilter(q.filter, filterCompilers));
132
133
  }
133
134
 
134
135
  if (q.search) {
135
- let searchFilter: StamhoofdFilter|null = null
136
+ let searchFilter: StamhoofdFilter | null = null;
136
137
 
137
138
  // is phone?
138
139
  if (!searchFilter && q.search.match(/^\+?[0-9\s-]+$/)) {
139
140
  // Try to format as phone so we have 1:1 space matches
140
141
  try {
141
- const phoneNumber = parsePhoneNumber(q.search, (Context.i18n.country as Country) || Country.Belgium)
142
+ const phoneNumber = parsePhoneNumber(q.search, (Context.i18n.country as Country) || Country.Belgium);
142
143
  if (phoneNumber && phoneNumber.isValid()) {
143
144
  const formatted = phoneNumber.formatInternational();
144
145
  searchFilter = {
145
146
  $or: [
146
147
  {
147
148
  phone: {
148
- $eq: formatted
149
- }
149
+ $eq: formatted,
150
+ },
150
151
  },
151
152
  {
152
153
  parentPhone: {
153
- $eq: formatted
154
- }
154
+ $eq: formatted,
155
+ },
155
156
  },
156
157
  {
157
158
  unverifiedPhone: {
158
- $eq: formatted
159
- }
160
- }
161
- ]
162
- }
163
-
159
+ $eq: formatted,
160
+ },
161
+ },
162
+ ],
163
+ };
164
164
  }
165
- } catch (e) {
166
- console.error('Failed to parse phone number', q.search, e)
165
+ }
166
+ catch (e) {
167
+ console.error('Failed to parse phone number', q.search, e);
167
168
  }
168
169
  }
169
170
 
170
- // Is lidnummer?
171
- if (!searchFilter && q.search.match(/^[0-9]{4}-[0-9]{6}-[0-9]{1,2}$/) || q.search.match(/^[0-9]{10}$/)) {
171
+ // Is lidnummer?
172
+ if (!searchFilter && (q.search.match(/^[0-9]{4}-[0-9]{6}-[0-9]{1,2}$/) || q.search.match(/^[0-9]{10}$/))) {
172
173
  searchFilter = {
173
174
  memberNumber: {
174
- $eq: q.search
175
- }
176
- }
175
+ $eq: q.search,
176
+ },
177
+ };
177
178
  }
178
179
 
179
180
  // Two search modes:
180
181
  // e-mail or name based searching
181
182
  if (searchFilter) {
182
183
  // already done
183
- } else if (q.search.includes('@')) {
184
+ }
185
+ else if (q.search.includes('@')) {
184
186
  const isCompleteAddress = DataValidator.isEmailValid(q.search);
185
187
 
186
188
  // Member email address contains, or member parent contains
187
189
  searchFilter = {
188
- '$or': [
190
+ $or: [
189
191
  {
190
192
  email: {
191
- [(isCompleteAddress ? '$eq' : '$contains')]: q.search
192
- }
193
+ [(isCompleteAddress ? '$eq' : '$contains')]: q.search,
194
+ },
193
195
  },
194
196
  {
195
197
  parentEmail: {
196
- [(isCompleteAddress ? '$eq' : '$contains')]: q.search
197
- }
198
+ [(isCompleteAddress ? '$eq' : '$contains')]: q.search,
199
+ },
198
200
  },
199
201
  {
200
202
  unverifiedEmail: {
201
- [(isCompleteAddress ? '$eq' : '$contains')]: q.search
202
- }
203
- }
204
- ]
205
- } as any as StamhoofdFilter
206
- } else {
203
+ [(isCompleteAddress ? '$eq' : '$contains')]: q.search,
204
+ },
205
+ },
206
+ ],
207
+ } as any as StamhoofdFilter;
208
+ }
209
+ else {
207
210
  searchFilter = {
208
211
  name: {
209
- $contains: q.search
210
- }
211
- }
212
+ $contains: q.search,
213
+ },
214
+ };
212
215
  }
213
216
 
214
217
  // todo: Address search detection
215
218
 
216
219
  if (searchFilter) {
217
- query.where(compileToSQLFilter(searchFilter, filterCompilers))
220
+ query.where(compileToSQLFilter(searchFilter, filterCompilers));
218
221
  }
219
222
  }
220
223
 
221
224
  if (q instanceof LimitedFilteredRequest) {
222
225
  if (q.pageFilter) {
223
- query.where(compileToSQLFilter(q.pageFilter, filterCompilers))
226
+ query.where(compileToSQLFilter(q.pageFilter, filterCompilers));
224
227
  }
225
228
 
226
- q.sort = assertSort(q.sort, [{key: 'id'}])
227
- query.orderBy(compileToSQLSorter(q.sort, sorters))
228
- query.limit(q.limit)
229
+ q.sort = assertSort(q.sort, [{ key: 'id' }]);
230
+ query.orderBy(compileToSQLSorter(q.sort, sorters));
231
+ query.limit(q.limit);
229
232
  }
230
-
231
- return query
233
+
234
+ return query;
232
235
  }
233
236
 
234
237
  static async buildData(requestQuery: LimitedFilteredRequest) {
235
- const query = await GetMembersEndpoint.buildQuery(requestQuery)
236
- const data = await query.fetch()
237
-
238
+ const query = await GetMembersEndpoint.buildQuery(requestQuery);
239
+ const data = await query.fetch();
240
+
238
241
  const memberIds = data.map((r) => {
239
242
  if (typeof r.members.id === 'string') {
240
- return r.members.id
243
+ return r.members.id;
241
244
  }
242
- throw new Error('Expected string')
245
+ throw new Error('Expected string');
243
246
  });
244
247
 
245
- const _members = await Member.getBlobByIds(...memberIds)
248
+ const _members = await Member.getBlobByIds(...memberIds);
246
249
  // Make sure members is in same order as memberIds
247
- const members = memberIds.map(id => _members.find(m => m.id === id)!)
250
+ const members = memberIds.map(id => _members.find(m => m.id === id)!);
248
251
 
249
252
  for (const member of members) {
250
253
  if (!await Context.auth.canAccessMember(member, PermissionLevel.Read)) {
251
- throw Context.auth.error()
254
+ throw Context.auth.error();
252
255
  }
253
256
  }
254
257
 
255
- let next: LimitedFilteredRequest|undefined;
258
+ let next: LimitedFilteredRequest | undefined;
256
259
 
257
260
  if (memberIds.length >= requestQuery.limit) {
258
261
  const lastObject = members[members.length - 1];
@@ -263,8 +266,8 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
263
266
  pageFilter: nextFilter,
264
267
  sort: requestQuery.sort,
265
268
  limit: requestQuery.limit,
266
- search: requestQuery.search
267
- })
269
+ search: requestQuery.search,
270
+ });
268
271
 
269
272
  if (JSON.stringify(nextFilter) === JSON.stringify(requestQuery.pageFilter)) {
270
273
  console.error('Found infinite loading loop for', requestQuery);
@@ -274,13 +277,13 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
274
277
 
275
278
  return new PaginatedResponse<MembersBlob, LimitedFilteredRequest>({
276
279
  results: await AuthenticatedStructures.membersBlob(members),
277
- next
280
+ next,
278
281
  });
279
282
  }
280
283
 
281
284
  async handle(request: DecodedRequest<Params, Query, Body>) {
282
285
  await Context.setOptionalOrganizationScope();
283
- await Context.authenticate()
286
+ await Context.authenticate();
284
287
 
285
288
  const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
286
289
 
@@ -288,20 +291,20 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
288
291
  throw new SimpleError({
289
292
  code: 'invalid_field',
290
293
  field: 'limit',
291
- message: 'Limit can not be more than ' + maxLimit
292
- })
294
+ message: 'Limit can not be more than ' + maxLimit,
295
+ });
293
296
  }
294
297
 
295
298
  if (request.query.limit < 1) {
296
299
  throw new SimpleError({
297
300
  code: 'invalid_field',
298
301
  field: 'limit',
299
- message: 'Limit can not be less than 1'
300
- })
302
+ message: 'Limit can not be less than 1',
303
+ });
301
304
  }
302
-
305
+
303
306
  return new Response(
304
- await GetMembersEndpoint.buildData(request.query)
307
+ await GetMembersEndpoint.buildData(request.query),
305
308
  );
306
309
  }
307
310
  }