@stamhoofd/backend 1.0.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 (150) hide show
  1. package/.env.template.json +63 -0
  2. package/.eslintrc.js +61 -0
  3. package/README.md +40 -0
  4. package/index.ts +172 -0
  5. package/jest.config.js +11 -0
  6. package/migrations.ts +33 -0
  7. package/package.json +48 -0
  8. package/src/crons.ts +845 -0
  9. package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
  10. package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
  11. package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
  12. package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
  13. package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
  14. package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
  15. package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
  16. package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
  17. package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
  18. package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
  19. package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
  20. package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
  21. package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
  22. package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
  23. package/src/endpoints/auth/SignupEndpoint.ts +107 -0
  24. package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
  25. package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
  26. package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
  27. package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
  28. package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
  29. package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
  30. package/src/endpoints/global/files/UploadFile.ts +147 -0
  31. package/src/endpoints/global/files/UploadImage.ts +119 -0
  32. package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
  33. package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
  34. package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
  35. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
  36. package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
  37. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
  38. package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
  39. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
  40. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
  41. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
  42. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
  43. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
  44. package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
  45. package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
  46. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
  47. package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
  48. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
  49. package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
  50. package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
  51. package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
  52. package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
  53. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
  54. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
  55. package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
  56. package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
  57. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
  58. package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
  59. package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
  60. package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
  61. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
  62. package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
  63. package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
  64. package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
  65. package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
  66. package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
  67. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
  68. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
  69. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
  70. package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
  71. package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
  72. package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
  73. package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
  74. package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
  75. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
  76. package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
  77. package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
  78. package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
  79. package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
  80. package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
  81. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
  82. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
  83. package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
  84. package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
  85. package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
  86. package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
  87. package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
  88. package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
  89. package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
  90. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
  91. package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
  92. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
  93. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
  94. package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
  95. package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
  96. package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
  97. package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
  98. package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
  99. package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
  100. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
  101. package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
  102. package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
  103. package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
  104. package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
  105. package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
  106. package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
  107. package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
  108. package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
  109. package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
  110. package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
  111. package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
  112. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
  113. package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
  114. package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
  115. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
  116. package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
  117. package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
  118. package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
  119. package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
  120. package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
  121. package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
  122. package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
  123. package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
  124. package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
  125. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
  126. package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
  127. package/src/helpers/AddressValidator.test.ts +40 -0
  128. package/src/helpers/AddressValidator.ts +256 -0
  129. package/src/helpers/AdminPermissionChecker.ts +1031 -0
  130. package/src/helpers/AuthenticatedStructures.ts +158 -0
  131. package/src/helpers/BuckarooHelper.ts +279 -0
  132. package/src/helpers/CheckSettlements.ts +215 -0
  133. package/src/helpers/Context.ts +202 -0
  134. package/src/helpers/CookieHelper.ts +45 -0
  135. package/src/helpers/ForwardHandler.test.ts +216 -0
  136. package/src/helpers/ForwardHandler.ts +140 -0
  137. package/src/helpers/OpenIDConnectHelper.ts +284 -0
  138. package/src/helpers/StripeHelper.ts +293 -0
  139. package/src/helpers/StripePayoutChecker.ts +188 -0
  140. package/src/middleware/ContextMiddleware.ts +16 -0
  141. package/src/migrations/1646578856-validate-addresses.ts +60 -0
  142. package/src/seeds/0000000000-example.ts +13 -0
  143. package/src/seeds/1715028563-user-permissions.ts +52 -0
  144. package/tests/e2e/stock.test.ts +2120 -0
  145. package/tests/e2e/tickets.test.ts +926 -0
  146. package/tests/helpers/StripeMocker.ts +362 -0
  147. package/tests/helpers/TestServer.ts +21 -0
  148. package/tests/jest.global.setup.ts +29 -0
  149. package/tests/jest.setup.ts +59 -0
  150. package/tsconfig.json +42 -0
@@ -0,0 +1,57 @@
1
+ import { AutoEncoder, BooleanDecoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
2
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
+ import { SimpleError } from '@simonbackx/simple-errors';
4
+ import { EmailAddress } from '@stamhoofd/email';
5
+
6
+ type Params = Record<string, never>;
7
+ type Query = undefined;
8
+
9
+ class Body extends AutoEncoder {
10
+ @field({ decoder: StringDecoder })
11
+ id: string
12
+
13
+ @field({ decoder: StringDecoder })
14
+ token: string
15
+
16
+ @field({ decoder: BooleanDecoder, optional: true })
17
+ unsubscribedMarketing?: boolean
18
+
19
+ @field({ decoder: BooleanDecoder, optional: true })
20
+ unsubscribedAll?: boolean
21
+ }
22
+
23
+ type ResponseBody = undefined;
24
+
25
+ export class ManageEmailAddressEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
26
+ bodyDecoder = Body as Decoder<Body>;
27
+
28
+ protected doesMatch(request: Request): [true, Params] | [false] {
29
+ if (request.method != "POST") {
30
+ return [false];
31
+ }
32
+
33
+ const params = Endpoint.parseParameters(request.url, "/email/manage", {});
34
+
35
+ if (params) {
36
+ return [true, params as Params];
37
+ }
38
+ return [false];
39
+ }
40
+
41
+ async handle(request: DecodedRequest<Params, Query, Body>) {
42
+ const email = await EmailAddress.getByID(request.body.id)
43
+ if (!email || email.token !== request.body.token || request.body.token.length < 10 || request.body.id.length < 10) {
44
+ throw new SimpleError({
45
+ code: "invalid_fields",
46
+ message: "Invalid token or id",
47
+ human: "Deze link is vervallen. Probeer het opnieuw in een recentere e-mail"
48
+ })
49
+ }
50
+
51
+ email.unsubscribedAll = request.body.unsubscribedAll ?? email.unsubscribedAll
52
+ email.unsubscribedMarketing = request.body.unsubscribedMarketing ?? email.unsubscribedMarketing
53
+
54
+ await email.save()
55
+ return new Response(undefined);
56
+ }
57
+ }
@@ -0,0 +1,147 @@
1
+
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { SimpleError } from '@simonbackx/simple-errors';
4
+ import { File } from '@stamhoofd/structures';
5
+ import { Formatter } from '@stamhoofd/utility';
6
+ import AWS from 'aws-sdk';
7
+ import formidable from 'formidable';
8
+ import { promises as fs } from "fs";
9
+ import { v4 as uuidv4 } from "uuid";
10
+
11
+ import { Context } from '../../../helpers/Context';
12
+
13
+ type Params = Record<string, never>;
14
+ type Query = {};
15
+ type Body = undefined
16
+ type ResponseBody = File
17
+
18
+
19
+ interface FormidableFile {
20
+ // The size of the uploaded file in bytes.
21
+ // If the file is still being uploaded (see `'fileBegin'` event),
22
+ // this property says how many bytes of the file have been written to disk yet.
23
+ size: number;
24
+
25
+ // The path this file is being written to. You can modify this in the `'fileBegin'` event in
26
+ // case you are unhappy with the way formidable generates a temporary path for your files.
27
+ filepath: string;
28
+
29
+ // The name this file had according to the uploading client.
30
+ originalFilename: string | null;
31
+
32
+ // The mime type of this file, according to the uploading client.
33
+ mimetype: string | null;
34
+ }
35
+
36
+ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
37
+ protected doesMatch(request: Request): [true, Params] | [false] {
38
+ if (request.method != "POST") {
39
+ return [false];
40
+ }
41
+
42
+ const params = Endpoint.parseParameters(request.url, "/upload-file", {});
43
+
44
+ if (params) {
45
+ return [true, params as Params];
46
+ }
47
+
48
+ return [false];
49
+ }
50
+
51
+ async handle(request: DecodedRequest<Params, Query, Body>) {
52
+ await Context.setOptionalOrganizationScope()
53
+ await Context.authenticate();
54
+
55
+ if (!Context.auth.canUpload()) {
56
+ throw Context.auth.error()
57
+ }
58
+
59
+ if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
60
+ throw new SimpleError({
61
+ code: "not_available",
62
+ message: "This endpoint is temporarily not available",
63
+ statusCode: 503
64
+ })
65
+ }
66
+
67
+ if (!request.request.request) {
68
+ throw new Error("Not supported without real request")
69
+ }
70
+
71
+ const form = formidable({ maxFileSize: 20 * 1024 * 1024, maxFields: 1, keepExtensions: true });
72
+ const file = await new Promise<FormidableFile>((resolve, reject) => {
73
+ if (!request.request.request) {
74
+ reject(new SimpleError({
75
+ code: "invalid_request",
76
+ message: "Invalid request",
77
+ statusCode: 500
78
+ }));
79
+ return;
80
+ }
81
+ form.parse(request.request.request, (err, fields, files) => {
82
+ if (err) {
83
+ reject(err);
84
+ return;
85
+ }
86
+
87
+ if (!files.file || !Array.isArray(files.file) || files.file.length !== 1){
88
+ reject(new SimpleError({
89
+ code: "missing_field",
90
+ message: "Missing file",
91
+ field: "file"
92
+ }))
93
+ return;
94
+ }
95
+
96
+ resolve(files.file[0]);
97
+ });
98
+ });
99
+
100
+
101
+ if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
102
+ throw new SimpleError({
103
+ code: "not_available",
104
+ message: "Uploading is not available",
105
+ statusCode: 503
106
+ })
107
+ }
108
+
109
+ const fileContent = await fs.readFile(file.filepath);
110
+
111
+ const s3 = new AWS.S3({
112
+ endpoint: STAMHOOFD.SPACES_ENDPOINT,
113
+ accessKeyId: STAMHOOFD.SPACES_KEY,
114
+ secretAccessKey: STAMHOOFD.SPACES_SECRET
115
+ });
116
+
117
+ let prefix = (STAMHOOFD.SPACES_PREFIX ?? "")
118
+ if (prefix.length > 0) {
119
+ prefix += "/"
120
+ }
121
+
122
+ // Also include the source, in private mode
123
+ const fileId = uuidv4();
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);
127
+ const params = {
128
+ Bucket: STAMHOOFD.SPACES_BUCKET,
129
+ Key: key,
130
+ Body: fileContent, // TODO
131
+ ContentType: file.mimetype ?? "application/pdf",
132
+ ACL: "public-read"
133
+ };
134
+
135
+ const fileStruct = new File({
136
+ id: fileId,
137
+ server: "https://"+STAMHOOFD.SPACES_BUCKET+"."+STAMHOOFD.SPACES_ENDPOINT,
138
+ path: key,
139
+ size: fileContent.length,
140
+ name: file.originalFilename
141
+ });
142
+
143
+ await s3.putObject(params).promise();
144
+
145
+ return new Response(fileStruct);
146
+ }
147
+ }
@@ -0,0 +1,119 @@
1
+
2
+ import { Decoder, ObjectData } from '@simonbackx/simple-encoding';
3
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
+ import { SimpleError } from '@simonbackx/simple-errors';
5
+ import { Image } from '@stamhoofd/models';
6
+ import { Image as ImageStruct, ResolutionRequest } from '@stamhoofd/structures';
7
+ import formidable from 'formidable';
8
+ import { promises as fs } from "fs";
9
+
10
+ import { Context } from '../../../helpers/Context';
11
+
12
+ type Params = Record<string, never>;
13
+ type Query = {};
14
+ type Body = undefined
15
+ type ResponseBody = ImageStruct
16
+
17
+ 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;
22
+
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;
26
+
27
+ // The name this file had according to the uploading client.
28
+ originalFilename: string | null;
29
+
30
+ // The mime type of this file, according to the uploading client.
31
+ mimetype: string | null;
32
+ }
33
+
34
+ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
35
+ protected doesMatch(request: Request): [true, Params] | [false] {
36
+ if (request.method != "POST") {
37
+ return [false];
38
+ }
39
+
40
+ const params = Endpoint.parseParameters(request.url, "/upload-image", {});
41
+
42
+ if (params) {
43
+ return [true, params as Params];
44
+ }
45
+
46
+ return [false];
47
+ }
48
+
49
+ async handle(request: DecodedRequest<Params, Query, Body>) {
50
+ await Context.setOptionalOrganizationScope()
51
+ await Context.authenticate();
52
+
53
+ if (!Context.auth.canUpload()) {
54
+ throw Context.auth.error()
55
+ }
56
+
57
+ if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
58
+ throw new SimpleError({
59
+ code: "not_available",
60
+ message: "This endpoint is temporarily not available",
61
+ statusCode: 503
62
+ })
63
+ }
64
+
65
+ if (!request.request.request) {
66
+ throw new Error("Not supported without real request")
67
+ }
68
+
69
+ const form = formidable({
70
+ maxFileSize: 5 * 1024 * 1024,
71
+ keepExtensions: true,
72
+ maxFiles: 1
73
+ });
74
+
75
+ const [file, resolutions] = await new Promise<[FormidableFile, ResolutionRequest[]]>((resolve, reject) => {
76
+ if (!request.request.request) {
77
+ reject(new SimpleError({
78
+ code: "invalid_request",
79
+ message: "Invalid request",
80
+ statusCode: 500
81
+ }));
82
+ return;
83
+ }
84
+
85
+ form.parse(request.request.request, (err, fields, files) => {
86
+ if (err) {
87
+ reject(err);
88
+ return;
89
+ }
90
+ if (!fields.resolutions || !Array.isArray(fields.resolutions) || fields.resolutions.length !== 1){
91
+ reject(new SimpleError({
92
+ code: "missing_field",
93
+ message: "Field resolutions is required",
94
+ field: "resolutions"
95
+ }))
96
+ return;
97
+ }
98
+ if (!files.file || !Array.isArray(files.file) || files.file.length !== 1){
99
+ reject(new SimpleError({
100
+ code: "missing_field",
101
+ message: "Missing file",
102
+ field: "file"
103
+ }))
104
+ return;
105
+ }
106
+ try {
107
+ const resolutions = new ObjectData(JSON.parse(fields.resolutions[0]), { version: request.request.getVersion() }).array(ResolutionRequest as Decoder<ResolutionRequest>)
108
+ resolve([files.file[0], resolutions]);
109
+ } catch (e) {
110
+ reject(e)
111
+ }
112
+ });
113
+ });
114
+
115
+ const fileContent = await fs.readFile(file.filepath);
116
+ const image = await Image.create(fileContent, file.mimetype ?? undefined, resolutions)
117
+ return new Response(ImageStruct.create(image));
118
+ }
119
+ }
@@ -0,0 +1,76 @@
1
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { Member, MemberWithRegistrations } from "@stamhoofd/models";
3
+
4
+ import { MembersBlob } from "@stamhoofd/structures";
5
+ import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
6
+ import { Context } from "../../../helpers/Context";
7
+ type Params = { id: string };
8
+ type Query = undefined
9
+ type Body = undefined
10
+ type ResponseBody = MembersBlob
11
+
12
+ /**
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
14
+ */
15
+
16
+ export class GetMemberFamilyEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
17
+ protected doesMatch(request: Request): [true, Params] | [false] {
18
+ if (request.method != "GET") {
19
+ return [false];
20
+ }
21
+
22
+ const params = Endpoint.parseParameters(request.url, "/organization/members/@id/family", { id: String});
23
+
24
+ if (params) {
25
+ return [true, params as Params];
26
+ }
27
+ return [false];
28
+ }
29
+
30
+ async handle(request: DecodedRequest<Params, Query, Body>) {
31
+ const organization = await Context.setOptionalOrganizationScope();
32
+ await Context.authenticate()
33
+
34
+ // Fast throw first (more in depth checking for patches later)
35
+ if (organization) {
36
+ if (!await Context.auth.hasSomeAccess(organization.id)) {
37
+ throw Context.auth.error()
38
+ }
39
+ } else {
40
+ if (!Context.auth.hasSomePlatformAccess()) {
41
+ throw Context.auth.error()
42
+ }
43
+ }
44
+
45
+ const members = (await Member.getFamilyWithRegistrations(request.params.id))
46
+
47
+ let foundMember = false
48
+
49
+ const validatedMembers: MemberWithRegistrations[] = []
50
+
51
+ for (const member of members) {
52
+ if (member.id === request.params.id) {
53
+ foundMember = true;
54
+
55
+ // Check access to this member (this will automatically give access to the family)
56
+ if (!await Context.auth.canAccessMember(member)) {
57
+ throw Context.auth.error("Je hebt geen toegang tot dit lid")
58
+ }
59
+ validatedMembers.push(member)
60
+ continue;
61
+ }
62
+ if (await Context.auth.canAccessMember(member)) {
63
+ // Remove from result
64
+ validatedMembers.push(member)
65
+ }
66
+ }
67
+
68
+ if (!foundMember) {
69
+ throw Context.auth.error("Je hebt geen toegang tot dit lid")
70
+ }
71
+
72
+ return new Response(
73
+ await AuthenticatedStructures.membersBlob(validatedMembers)
74
+ );
75
+ }
76
+ }
@@ -0,0 +1,43 @@
1
+ import { Decoder } from '@simonbackx/simple-encoding';
2
+ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
+ import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
4
+
5
+ import { Context } from '../../../helpers/Context';
6
+ import { GetMembersEndpoint } from './GetMembersEndpoint';
7
+
8
+ type Params = Record<string, never>;
9
+ type Query = CountFilteredRequest;
10
+ type Body = undefined;
11
+ type ResponseBody = CountResponse;
12
+
13
+ export class GetMembersCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
14
+ queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>
15
+
16
+ protected doesMatch(request: Request): [true, Params] | [false] {
17
+ if (request.method != "GET") {
18
+ return [false];
19
+ }
20
+
21
+ const params = Endpoint.parseParameters(request.url, "/members/count", {});
22
+
23
+ if (params) {
24
+ return [true, params as Params];
25
+ }
26
+ return [false];
27
+ }
28
+
29
+ async handle(request: DecodedRequest<Params, Query, Body>) {
30
+ await Context.setOptionalOrganizationScope();
31
+ await Context.authenticate()
32
+ const query = await GetMembersEndpoint.buildQuery(request.query)
33
+
34
+ const count = await query
35
+ .count();
36
+
37
+ return new Response(
38
+ CountResponse.create({
39
+ count
40
+ })
41
+ );
42
+ }
43
+ }