@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.
- package/.env.template.json +63 -0
- package/.eslintrc.js +61 -0
- package/README.md +40 -0
- package/index.ts +172 -0
- package/jest.config.js +11 -0
- package/migrations.ts +33 -0
- package/package.json +48 -0
- package/src/crons.ts +845 -0
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
- package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
- package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
- package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
- package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
- package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
- package/src/endpoints/auth/SignupEndpoint.ts +107 -0
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
- package/src/endpoints/global/files/UploadFile.ts +147 -0
- package/src/endpoints/global/files/UploadImage.ts +119 -0
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
- package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
- package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
- package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
- package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
- package/src/helpers/AddressValidator.test.ts +40 -0
- package/src/helpers/AddressValidator.ts +256 -0
- package/src/helpers/AdminPermissionChecker.ts +1031 -0
- package/src/helpers/AuthenticatedStructures.ts +158 -0
- package/src/helpers/BuckarooHelper.ts +279 -0
- package/src/helpers/CheckSettlements.ts +215 -0
- package/src/helpers/Context.ts +202 -0
- package/src/helpers/CookieHelper.ts +45 -0
- package/src/helpers/ForwardHandler.test.ts +216 -0
- package/src/helpers/ForwardHandler.ts +140 -0
- package/src/helpers/OpenIDConnectHelper.ts +284 -0
- package/src/helpers/StripeHelper.ts +293 -0
- package/src/helpers/StripePayoutChecker.ts +188 -0
- package/src/middleware/ContextMiddleware.ts +16 -0
- package/src/migrations/1646578856-validate-addresses.ts +60 -0
- package/src/seeds/0000000000-example.ts +13 -0
- package/src/seeds/1715028563-user-permissions.ts +52 -0
- package/tests/e2e/stock.test.ts +2120 -0
- package/tests/e2e/tickets.test.ts +926 -0
- package/tests/helpers/StripeMocker.ts +362 -0
- package/tests/helpers/TestServer.ts +21 -0
- package/tests/jest.global.setup.ts +29 -0
- package/tests/jest.setup.ts +59 -0
- 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
|
+
}
|