@stamhoofd/backend 2.39.0 → 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
@@ -6,7 +6,7 @@ import { ArchiverWriterAdapter, exportToExcel, XlsxTransformerSheet, XlsxWriter
6
6
  import { Platform, RateLimiter, sendEmailTemplate } from '@stamhoofd/models';
7
7
  import { QueueHandler } from '@stamhoofd/queues';
8
8
  import { EmailTemplateType, ExcelExportRequest, ExcelExportResponse, ExcelExportType, IPaginatedResponse, LimitedFilteredRequest, Replacement, Version } from '@stamhoofd/structures';
9
- import { sleep } from "@stamhoofd/utility";
9
+ import { sleep } from '@stamhoofd/utility';
10
10
  import { Context } from '../../../helpers/Context';
11
11
  import { fetchToAsyncIterator } from '../../../helpers/fetchToAsyncIterator';
12
12
  import { FileCache } from '../../../helpers/FileCache';
@@ -17,32 +17,32 @@ type Body = ExcelExportRequest;
17
17
  type ResponseBody = ExcelExportResponse;
18
18
 
19
19
  type ExcelExporter<T> = {
20
- fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T[], LimitedFilteredRequest>>
21
- sheets: XlsxTransformerSheet<T, unknown>[]
22
- }
20
+ fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T[], LimitedFilteredRequest>>;
21
+ sheets: XlsxTransformerSheet<T, unknown>[];
22
+ };
23
23
 
24
24
  export const limiter = new RateLimiter({
25
25
  limits: [
26
- {
26
+ {
27
27
  // Max 200 per day
28
28
  limit: 200,
29
- duration: 60 * 1000 * 60 * 24
30
- }
31
- ]
29
+ duration: 60 * 1000 * 60 * 24,
30
+ },
31
+ ],
32
32
  });
33
33
 
34
34
  export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
35
- bodyDecoder = ExcelExportRequest as Decoder<ExcelExportRequest>
35
+ bodyDecoder = ExcelExportRequest as Decoder<ExcelExportRequest>;
36
36
 
37
37
  // Other endpoints can register exports here
38
- static loaders: Map<ExcelExportType, ExcelExporter<unknown>> = new Map()
38
+ static loaders: Map<ExcelExportType, ExcelExporter<unknown>> = new Map();
39
39
 
40
40
  protected doesMatch(request: Request): [true, Params] | [false] {
41
- if (request.method != "POST") {
41
+ if (request.method !== 'POST') {
42
42
  return [false];
43
43
  }
44
44
 
45
- const params = Endpoint.parseParameters(request.url, "/export/excel/@type", {type: String});
45
+ const params = Endpoint.parseParameters(request.url, '/export/excel/@type', { type: String });
46
46
 
47
47
  if (params) {
48
48
  return [true, params as Params];
@@ -52,33 +52,33 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
52
52
 
53
53
  async handle(request: DecodedRequest<Params, Query, Body>) {
54
54
  await Context.setOptionalOrganizationScope();
55
- const {user} = await Context.authenticate()
55
+ const { user } = await Context.authenticate();
56
56
 
57
57
  if (user.isApiUser) {
58
58
  throw new SimpleError({
59
- code: "not_allowed",
60
- message: "API users are not allowed to export to Excel. The Excel export endpoint has a side effect of sending e-mails. Please use normal API endpoints to get the data you need.",
61
- statusCode: 403
62
- })
59
+ code: 'not_allowed',
60
+ message: 'API users are not allowed to export to Excel. The Excel export endpoint has a side effect of sending e-mails. Please use normal API endpoints to get the data you need.',
61
+ statusCode: 403,
62
+ });
63
63
  }
64
64
 
65
65
  if (QueueHandler.isRunning('user-export-to-excel-' + user.id)) {
66
66
  throw new SimpleError({
67
- code: "not_allowed",
68
- message: "Export is pending",
67
+ code: 'not_allowed',
68
+ message: 'Export is pending',
69
69
  human: 'Je hebt momenteel al een Excel export lopen. Wacht tot die klaar is voor je een nieuwe export start.',
70
- statusCode: 403
71
- })
70
+ statusCode: 403,
71
+ });
72
72
  }
73
73
 
74
74
  const loader = ExportToExcelEndpoint.loaders.get(request.params.type as ExcelExportType);
75
-
75
+
76
76
  if (!loader) {
77
77
  throw new SimpleError({
78
- code: "invalid_type",
79
- message: "Invalid type " + request.params.type,
80
- statusCode: 400
81
- })
78
+ code: 'invalid_type',
79
+ message: 'Invalid type ' + request.params.type,
80
+ statusCode: 400,
81
+ });
82
82
  }
83
83
 
84
84
  limiter.track(user.id, 1);
@@ -91,17 +91,16 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
91
91
  if (sendEmail) {
92
92
  await sendEmailTemplate(null, {
93
93
  template: {
94
- type: EmailTemplateType.ExcelExportSucceeded
94
+ type: EmailTemplateType.ExcelExportSucceeded,
95
95
  },
96
96
  recipients: [
97
97
  user.createRecipient(Replacement.create({
98
98
  token: 'downloadUrl',
99
- value: url
100
- }))
99
+ value: url,
100
+ })),
101
101
  ],
102
- type: 'transactional'
103
- })
104
-
102
+ type: 'transactional',
103
+ });
105
104
  }
106
105
 
107
106
  return url;
@@ -109,32 +108,32 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
109
108
  if (sendEmail) {
110
109
  await sendEmailTemplate(null, {
111
110
  template: {
112
- type: EmailTemplateType.ExcelExportFailed
111
+ type: EmailTemplateType.ExcelExportFailed,
113
112
  },
114
113
  recipients: [
115
- user.createRecipient()
114
+ user.createRecipient(),
116
115
  ],
117
- type: 'transactional'
118
- })
116
+ type: 'transactional',
117
+ });
119
118
  }
120
- throw error
119
+ throw error;
121
120
  }),
122
- sleep(3000)
123
- ])
121
+ sleep(3000),
122
+ ]);
124
123
 
125
124
  if (typeof result === 'string') {
126
125
  return new Response(ExcelExportResponse.create({
127
- url: result
128
- }))
126
+ url: result,
127
+ }));
129
128
  }
130
-
129
+
131
130
  // We'll send an e-mail
132
131
  // Let the job know to send an e-mail when it is done
133
132
  sendEmail = true;
134
133
 
135
134
  return new Response(ExcelExportResponse.create({
136
- url: null
137
- }))
135
+ url: null,
136
+ }));
138
137
  }
139
138
 
140
139
  async job(loader: ExcelExporter<unknown>, request: ExcelExportRequest, type: string): Promise<string> {
@@ -145,7 +144,7 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
145
144
  // Estimate how long it will take.
146
145
  // If too long, we'll schedule it and write it to Digitalocean Spaces
147
146
  // Otherwise we'll just return the file directly
148
- const {file, stream} = await FileCache.getWriteStream('.xlsx');
147
+ const { file, stream } = await FileCache.getWriteStream('.xlsx');
149
148
 
150
149
  const zipWriterAdapter = new ArchiverWriterAdapter(stream);
151
150
  const writer = new XlsxWriter(zipWriterAdapter);
@@ -157,14 +156,14 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
157
156
  definitions: loader.sheets,
158
157
  writer,
159
158
  dataGenerator: fetchToAsyncIterator(request.filter, loader),
160
- filter: request.workbookFilter
161
- })
159
+ filter: request.workbookFilter,
160
+ });
162
161
 
163
- console.log('Done writing excel file')
162
+ console.log('Done writing excel file');
164
163
 
165
- const url = 'https://'+ STAMHOOFD.domains.api + '/v'+ Version +'/file-cache?file=' + encodeURIComponent(file) + '&name=' + encodeURIComponent(type)
164
+ const url = 'https://' + STAMHOOFD.domains.api + '/v' + Version + '/file-cache?file=' + encodeURIComponent(file) + '&name=' + encodeURIComponent(type);
166
165
  return url;
167
- }, 2)
166
+ }, 2);
168
167
  });
169
168
  }
170
169
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
1
  import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
3
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
3
  import { Readable } from 'node:stream';
@@ -9,11 +8,11 @@ import { RateLimiter } from '@stamhoofd/models';
9
8
 
10
9
  type Params = Record<never, never>;
11
10
  class Query extends AutoEncoder {
12
- @field({ decoder: StringDecoder})
13
- file: string
11
+ @field({ decoder: StringDecoder })
12
+ file: string;
14
13
 
15
14
  @field({ decoder: StringDecoder, optional: true })
16
- name?: string
15
+ name?: string;
17
16
  }
18
17
 
19
18
  type Body = undefined;
@@ -21,23 +20,23 @@ type ResponseBody = Readable;
21
20
 
22
21
  export const limiter = new RateLimiter({
23
22
  limits: [
24
- {
23
+ {
25
24
  // Max 200 per day
26
25
  limit: 200,
27
- duration: 60 * 1000 * 60 * 24
28
- }
29
- ]
26
+ duration: 60 * 1000 * 60 * 24,
27
+ },
28
+ ],
30
29
  });
31
30
 
32
31
  export class GetFileCache extends Endpoint<Params, Query, Body, ResponseBody> {
33
- queryDecoder = Query as Decoder<Query>
32
+ queryDecoder = Query as Decoder<Query>;
34
33
 
35
34
  protected doesMatch(request: Request): [true, Params] | [false] {
36
- if (request.method != "GET") {
35
+ if (request.method !== 'GET') {
37
36
  return [false];
38
37
  }
39
38
 
40
- const params = Endpoint.parseParameters(request.url, "/file-cache", {});
39
+ const params = Endpoint.parseParameters(request.url, '/file-cache', {});
41
40
 
42
41
  if (params) {
43
42
  return [true, params as Params];
@@ -51,7 +50,7 @@ export class GetFileCache extends Endpoint<Params, Query, Body, ResponseBody> {
51
50
  limiter.track(request.request.getIP(), 1);
52
51
 
53
52
  // Return readable stream
54
- const {stream, contentLength, extension} = await FileCache.read(request.query.file, 1);
53
+ const { stream, contentLength, extension } = await FileCache.read(request.query.file, 1);
55
54
 
56
55
  const response = new Response(stream);
57
56
  response.headers['Content-Type'] = 'application/octet-stream';
@@ -59,7 +58,8 @@ export class GetFileCache extends Endpoint<Params, Query, Body, ResponseBody> {
59
58
  if (request.query.name) {
60
59
  const slug = Formatter.fileSlug(request.query.name) + extension;
61
60
  response.headers['Content-Disposition'] = `attachment; filename="${slug}"`;
62
- } else {
61
+ }
62
+ else {
63
63
  response.headers['Content-Disposition'] = `attachment; filename="bestand${extension}"`;
64
64
  }
65
65
  response.headers['Content-Length'] = contentLength.toString();
@@ -1,46 +1,44 @@
1
-
2
1
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
2
  import { SimpleError } from '@simonbackx/simple-errors';
4
3
  import { File } from '@stamhoofd/structures';
5
4
  import { Formatter } from '@stamhoofd/utility';
6
5
  import AWS from 'aws-sdk';
7
6
  import formidable from 'formidable';
8
- import { promises as fs } from "fs";
9
- import { v4 as uuidv4 } from "uuid";
7
+ import { promises as fs } from 'fs';
8
+ import { v4 as uuidv4 } from 'uuid';
10
9
 
11
10
  import { Context } from '../../../helpers/Context';
12
11
  import { limiter } from './UploadImage';
13
12
 
14
13
  type Params = Record<string, never>;
15
- type Query = {};
16
- type Body = undefined
17
- type ResponseBody = File
18
-
14
+ type Query = Record<string, never>;
15
+ type Body = undefined;
16
+ type ResponseBody = File;
19
17
 
20
18
  interface FormidableFile {
21
- // The size of the uploaded file in bytes.
22
- // If the file is still being uploaded (see `'fileBegin'` event),
23
- // this property says how many bytes of the file have been written to disk yet.
24
- size: number;
19
+ // The size of the uploaded file in bytes.
20
+ // If the file is still being uploaded (see `'fileBegin'` event),
21
+ // this property says how many bytes of the file have been written to disk yet.
22
+ size: number;
25
23
 
26
- // The path this file is being written to. You can modify this in the `'fileBegin'` event in
27
- // case you are unhappy with the way formidable generates a temporary path for your files.
28
- filepath: string;
24
+ // The path this file is being written to. You can modify this in the `'fileBegin'` event in
25
+ // case you are unhappy with the way formidable generates a temporary path for your files.
26
+ filepath: string;
29
27
 
30
- // The name this file had according to the uploading client.
31
- originalFilename: string | null;
28
+ // The name this file had according to the uploading client.
29
+ originalFilename: string | null;
32
30
 
33
- // The mime type of this file, according to the uploading client.
34
- mimetype: string | null;
31
+ // The mime type of this file, according to the uploading client.
32
+ mimetype: string | null;
35
33
  }
36
34
 
37
- export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
35
+ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
38
36
  protected doesMatch(request: Request): [true, Params] | [false] {
39
- if (request.method != "POST") {
37
+ if (request.method !== 'POST') {
40
38
  return [false];
41
39
  }
42
40
 
43
- const params = Endpoint.parseParameters(request.url, "/upload-file", {});
41
+ const params = Endpoint.parseParameters(request.url, '/upload-file', {});
44
42
 
45
43
  if (params) {
46
44
  return [true, params as Params];
@@ -50,23 +48,23 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
50
48
  }
51
49
 
52
50
  async handle(request: DecodedRequest<Params, Query, Body>) {
53
- await Context.setOptionalOrganizationScope()
54
- const {user} = await Context.authenticate();
51
+ await Context.setOptionalOrganizationScope();
52
+ const { user } = await Context.authenticate();
55
53
 
56
54
  if (!Context.auth.canUpload()) {
57
- throw Context.auth.error()
55
+ throw Context.auth.error();
58
56
  }
59
57
 
60
58
  if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
61
59
  throw new SimpleError({
62
- code: "not_available",
63
- message: "This endpoint is temporarily not available",
64
- statusCode: 503
65
- })
60
+ code: 'not_available',
61
+ message: 'This endpoint is temporarily not available',
62
+ statusCode: 503,
63
+ });
66
64
  }
67
65
 
68
66
  if (!request.request.request) {
69
- throw new Error("Not supported without real request")
67
+ throw new Error('Not supported without real request');
70
68
  }
71
69
 
72
70
  limiter.track(user.id, 1);
@@ -75,9 +73,9 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
75
73
  const file = await new Promise<FormidableFile>((resolve, reject) => {
76
74
  if (!request.request.request) {
77
75
  reject(new SimpleError({
78
- code: "invalid_request",
79
- message: "Invalid request",
80
- statusCode: 500
76
+ code: 'invalid_request',
77
+ message: 'Invalid request',
78
+ statusCode: 500,
81
79
  }));
82
80
  return;
83
81
  }
@@ -87,26 +85,25 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
87
85
  return;
88
86
  }
89
87
 
90
- if (!files.file || !Array.isArray(files.file) || files.file.length !== 1){
88
+ if (!files.file || !Array.isArray(files.file) || files.file.length !== 1) {
91
89
  reject(new SimpleError({
92
- code: "missing_field",
93
- message: "Missing file",
94
- field: "file"
95
- }))
90
+ code: 'missing_field',
91
+ message: 'Missing file',
92
+ field: 'file',
93
+ }));
96
94
  return;
97
95
  }
98
-
96
+
99
97
  resolve(files.file[0]);
100
98
  });
101
99
  });
102
100
 
103
-
104
101
  if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
105
102
  throw new SimpleError({
106
- code: "not_available",
107
- message: "Uploading is not available",
108
- statusCode: 503
109
- })
103
+ code: 'not_available',
104
+ message: 'Uploading is not available',
105
+ statusCode: 503,
106
+ });
110
107
  }
111
108
 
112
109
  const fileContent = await fs.readFile(file.filepath);
@@ -114,33 +111,33 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
114
111
  const s3 = new AWS.S3({
115
112
  endpoint: STAMHOOFD.SPACES_ENDPOINT,
116
113
  accessKeyId: STAMHOOFD.SPACES_KEY,
117
- secretAccessKey: STAMHOOFD.SPACES_SECRET
114
+ secretAccessKey: STAMHOOFD.SPACES_SECRET,
118
115
  });
119
116
 
120
- let prefix = (STAMHOOFD.SPACES_PREFIX ?? "")
117
+ let prefix = (STAMHOOFD.SPACES_PREFIX ?? '');
121
118
  if (prefix.length > 0) {
122
- prefix += "/"
119
+ prefix += '/';
123
120
  }
124
-
121
+
125
122
  // Also include the source, in private mode
126
123
  const fileId = uuidv4();
127
- const uploadExt = file.mimetype == "application/pdf" ? "pdf" : "pdf"
128
- const filenameWithoutExt = file.originalFilename?.split(".").slice(0, -1).join(".") ?? fileId
129
- const key = prefix+(STAMHOOFD.environment ?? "development")+"/"+fileId+"/" + (Formatter.slug(filenameWithoutExt) +"."+uploadExt);
124
+ const uploadExt = file.mimetype == 'application/pdf' ? 'pdf' : 'pdf';
125
+ const filenameWithoutExt = file.originalFilename?.split('.').slice(0, -1).join('.') ?? fileId;
126
+ const key = prefix + (STAMHOOFD.environment ?? 'development') + '/' + fileId + '/' + (Formatter.slug(filenameWithoutExt) + '.' + uploadExt);
130
127
  const params = {
131
128
  Bucket: STAMHOOFD.SPACES_BUCKET,
132
129
  Key: key,
133
130
  Body: fileContent, // TODO
134
- ContentType: file.mimetype ?? "application/pdf",
135
- ACL: "public-read"
131
+ ContentType: file.mimetype ?? 'application/pdf',
132
+ ACL: 'public-read',
136
133
  };
137
134
 
138
135
  const fileStruct = new File({
139
136
  id: fileId,
140
- server: "https://"+STAMHOOFD.SPACES_BUCKET+"."+STAMHOOFD.SPACES_ENDPOINT,
137
+ server: 'https://' + STAMHOOFD.SPACES_BUCKET + '.' + STAMHOOFD.SPACES_ENDPOINT,
141
138
  path: key,
142
139
  size: fileContent.length,
143
- name: file.originalFilename
140
+ name: file.originalFilename,
144
141
  });
145
142
 
146
143
  await s3.putObject(params).promise();
@@ -1,53 +1,52 @@
1
-
2
1
  import { Decoder, ObjectData } 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 { Image, RateLimiter } from '@stamhoofd/models';
6
5
  import { Image as ImageStruct, ResolutionRequest } from '@stamhoofd/structures';
7
6
  import formidable from 'formidable';
8
- import { promises as fs } from "fs";
7
+ import { promises as fs } from 'fs';
9
8
 
10
9
  import { Context } from '../../../helpers/Context';
11
10
 
12
11
  type Params = Record<string, never>;
13
- type Query = {};
14
- type Body = undefined
15
- type ResponseBody = ImageStruct
12
+ type Query = Record<string, never>;
13
+ type Body = undefined;
14
+ type ResponseBody = ImageStruct;
16
15
 
17
16
  interface FormidableFile {
18
- // The size of the uploaded file in bytes.
19
- // If the file is still being uploaded (see `'fileBegin'` event),
20
- // this property says how many bytes of the file have been written to disk yet.
21
- size: number;
17
+ // The size of the uploaded file in bytes.
18
+ // If the file is still being uploaded (see `'fileBegin'` event),
19
+ // this property says how many bytes of the file have been written to disk yet.
20
+ size: number;
22
21
 
23
- // The path this file is being written to. You can modify this in the `'fileBegin'` event in
24
- // case you are unhappy with the way formidable generates a temporary path for your files.
25
- filepath: string;
22
+ // The path this file is being written to. You can modify this in the `'fileBegin'` event in
23
+ // case you are unhappy with the way formidable generates a temporary path for your files.
24
+ filepath: string;
26
25
 
27
- // The name this file had according to the uploading client.
28
- originalFilename: string | null;
26
+ // The name this file had according to the uploading client.
27
+ originalFilename: string | null;
29
28
 
30
- // The mime type of this file, according to the uploading client.
31
- mimetype: string | null;
29
+ // The mime type of this file, according to the uploading client.
30
+ mimetype: string | null;
32
31
  }
33
32
 
34
33
  export const limiter = new RateLimiter({
35
34
  limits: [
36
- {
35
+ {
37
36
  // Max 50 per hour
38
37
  limit: 50,
39
- duration: 60 * 1000 * 60
40
- }
41
- ]
38
+ duration: 60 * 1000 * 60,
39
+ },
40
+ ],
42
41
  });
43
42
 
44
- export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
43
+ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
45
44
  protected doesMatch(request: Request): [true, Params] | [false] {
46
- if (request.method != "POST") {
45
+ if (request.method !== 'POST') {
47
46
  return [false];
48
47
  }
49
48
 
50
- const params = Endpoint.parseParameters(request.url, "/upload-image", {});
49
+ const params = Endpoint.parseParameters(request.url, '/upload-image', {});
51
50
 
52
51
  if (params) {
53
52
  return [true, params as Params];
@@ -57,39 +56,39 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
57
56
  }
58
57
 
59
58
  async handle(request: DecodedRequest<Params, Query, Body>) {
60
- await Context.setOptionalOrganizationScope()
61
- const {user} = await Context.authenticate();
59
+ await Context.setOptionalOrganizationScope();
60
+ const { user } = await Context.authenticate();
62
61
 
63
62
  if (!Context.auth.canUpload()) {
64
- throw Context.auth.error()
63
+ throw Context.auth.error();
65
64
  }
66
65
 
67
66
  if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
68
67
  throw new SimpleError({
69
- code: "not_available",
70
- message: "This endpoint is temporarily not available",
71
- statusCode: 503
72
- })
68
+ code: 'not_available',
69
+ message: 'This endpoint is temporarily not available',
70
+ statusCode: 503,
71
+ });
73
72
  }
74
73
 
75
74
  if (!request.request.request) {
76
- throw new Error("Not supported without real request")
75
+ throw new Error('Not supported without real request');
77
76
  }
78
77
 
79
78
  limiter.track(user.id, 1);
80
79
 
81
- const form = formidable({
82
- maxFileSize: 5 * 1024 * 1024,
83
- keepExtensions: true,
84
- maxFiles: 1
80
+ const form = formidable({
81
+ maxFileSize: 5 * 1024 * 1024,
82
+ keepExtensions: true,
83
+ maxFiles: 1,
85
84
  });
86
85
 
87
86
  const [file, resolutions] = await new Promise<[FormidableFile, ResolutionRequest[]]>((resolve, reject) => {
88
87
  if (!request.request.request) {
89
88
  reject(new SimpleError({
90
- code: "invalid_request",
91
- message: "Invalid request",
92
- statusCode: 500
89
+ code: 'invalid_request',
90
+ message: 'Invalid request',
91
+ statusCode: 500,
93
92
  }));
94
93
  return;
95
94
  }
@@ -99,33 +98,34 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
99
98
  reject(err);
100
99
  return;
101
100
  }
102
- if (!fields.resolutions || !Array.isArray(fields.resolutions) || fields.resolutions.length !== 1){
101
+ if (!fields.resolutions || !Array.isArray(fields.resolutions) || fields.resolutions.length !== 1) {
103
102
  reject(new SimpleError({
104
- code: "missing_field",
105
- message: "Field resolutions is required",
106
- field: "resolutions"
107
- }))
103
+ code: 'missing_field',
104
+ message: 'Field resolutions is required',
105
+ field: 'resolutions',
106
+ }));
108
107
  return;
109
108
  }
110
- if (!files.file || !Array.isArray(files.file) || files.file.length !== 1){
109
+ if (!files.file || !Array.isArray(files.file) || files.file.length !== 1) {
111
110
  reject(new SimpleError({
112
- code: "missing_field",
113
- message: "Missing file",
114
- field: "file"
115
- }))
111
+ code: 'missing_field',
112
+ message: 'Missing file',
113
+ field: 'file',
114
+ }));
116
115
  return;
117
116
  }
118
- try {
119
- const resolutions = new ObjectData(JSON.parse(fields.resolutions[0]), { version: request.request.getVersion() }).array(ResolutionRequest as Decoder<ResolutionRequest>)
117
+ try {
118
+ const resolutions = new ObjectData(JSON.parse(fields.resolutions[0]), { version: request.request.getVersion() }).array(ResolutionRequest as Decoder<ResolutionRequest>);
120
119
  resolve([files.file[0], resolutions]);
121
- } catch (e) {
122
- reject(e)
120
+ }
121
+ catch (e) {
122
+ reject(e);
123
123
  }
124
124
  });
125
125
  });
126
126
 
127
127
  const fileContent = await fs.readFile(file.filepath);
128
- const image = await Image.create(fileContent, file.mimetype ?? undefined, resolutions)
128
+ const image = await Image.create(fileContent, file.mimetype ?? undefined, resolutions);
129
129
  return new Response(ImageStruct.create(image));
130
130
  }
131
131
  }