@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,153 @@
|
|
|
1
|
+
import { createMollieClient } from '@mollie/api-client';
|
|
2
|
+
import { AutoEncoder, BooleanDecoder,Decoder,field } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
4
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
5
|
+
import { MolliePayment, Organization } from "@stamhoofd/models";
|
|
6
|
+
import { Payment } from "@stamhoofd/models";
|
|
7
|
+
import { STInvoice } from "@stamhoofd/models";
|
|
8
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
9
|
+
import { PaymentMethod,PaymentProvider,PaymentStatus, STInvoice as STInvoiceStruct } from "@stamhoofd/structures";
|
|
10
|
+
type Params = {id: string};
|
|
11
|
+
class Query extends AutoEncoder {
|
|
12
|
+
@field({ decoder: BooleanDecoder, optional: true })
|
|
13
|
+
exchange = false
|
|
14
|
+
}
|
|
15
|
+
type Body = undefined
|
|
16
|
+
type ResponseBody = STInvoiceStruct | undefined;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 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
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export class ExchangeSTPaymentEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
23
|
+
queryDecoder = Query as Decoder<Query>
|
|
24
|
+
|
|
25
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
26
|
+
if (request.method != "POST") {
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const params = Endpoint.parseParameters(request.url, "/billing/payments/@id", {id: String});
|
|
31
|
+
|
|
32
|
+
if (params) {
|
|
33
|
+
return [true, params as Params];
|
|
34
|
+
}
|
|
35
|
+
return [false];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
39
|
+
const payment = await Payment.getByID(request.params.id)
|
|
40
|
+
if (!payment) {
|
|
41
|
+
throw new SimpleError({
|
|
42
|
+
code: "",
|
|
43
|
+
message: "Deze link is ongeldig"
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const invoices = await STInvoice.where({ paymentId: payment.id })
|
|
48
|
+
if (invoices.length > 1) {
|
|
49
|
+
console.error("Received more than 1 invoices for the same payment. Danger zone!")
|
|
50
|
+
throw new Error("Unexpected error")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (invoices.length == 0) {
|
|
54
|
+
console.error("Didn't found and invoice for a given payment!")
|
|
55
|
+
throw new Error("Unexpected error")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Not method on payment because circular references (not supprted in ts)
|
|
59
|
+
const invoice = invoices[0]
|
|
60
|
+
|
|
61
|
+
if (request.query.exchange) {
|
|
62
|
+
// Don't wait for exchanges
|
|
63
|
+
ExchangeSTPaymentEndpoint.pollStatus(payment, invoice).catch(e => {
|
|
64
|
+
console.error(e)
|
|
65
|
+
})
|
|
66
|
+
return new Response(undefined);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const updatedInvoice = await ExchangeSTPaymentEndpoint.pollStatus(payment, invoice)
|
|
70
|
+
|
|
71
|
+
if (!updatedInvoice) {
|
|
72
|
+
return new Response(undefined);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new Response(
|
|
76
|
+
await updatedInvoice.getStructure()
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static async pollStatus(payment: Payment, _invoice: STInvoice): Promise<STInvoice | undefined> {
|
|
81
|
+
// All invoice related logic needs to happen after each ather, not concurrently
|
|
82
|
+
return await QueueHandler.schedule("billing/invoices-"+_invoice.organizationId, async () => {
|
|
83
|
+
|
|
84
|
+
// Get a new copy of the invoice (is required to prevent concurrenty bugs)
|
|
85
|
+
const invoice = await STInvoice.getByID(_invoice.id)
|
|
86
|
+
if (!invoice || invoice.paidAt !== null) {
|
|
87
|
+
return invoice
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ((payment.provider === PaymentProvider.Mollie || (payment.provider === null && payment.method == PaymentMethod.DirectDebit)) && (payment.status == PaymentStatus.Pending || payment.status == PaymentStatus.Created || payment.status == PaymentStatus.Failed)) {
|
|
91
|
+
if (payment.method == PaymentMethod.Bancontact || payment.method == PaymentMethod.iDEAL || payment.method == PaymentMethod.CreditCard || payment.method == PaymentMethod.DirectDebit || payment.method == PaymentMethod.Transfer) {
|
|
92
|
+
// check status via mollie
|
|
93
|
+
const molliePayments = await MolliePayment.where({ paymentId: payment.id}, { limit: 1 })
|
|
94
|
+
if (molliePayments.length == 1) {
|
|
95
|
+
const molliePayment = molliePayments[0]
|
|
96
|
+
// check status
|
|
97
|
+
const apiKey = STAMHOOFD.MOLLIE_API_KEY
|
|
98
|
+
if (apiKey) {
|
|
99
|
+
const mollieClient = createMollieClient({ apiKey });
|
|
100
|
+
const mollieData = await mollieClient.payments.get(molliePayment.mollieId)
|
|
101
|
+
|
|
102
|
+
console.log(mollieData) // log to log files to check issues
|
|
103
|
+
|
|
104
|
+
const details = (mollieData.details as any)
|
|
105
|
+
if (details?.cardNumber) {
|
|
106
|
+
payment.iban = "xxxx xxxx xxxx "+details.cardNumber
|
|
107
|
+
}
|
|
108
|
+
if (details?.cardHolder) {
|
|
109
|
+
payment.ibanName = details.cardHolder
|
|
110
|
+
}
|
|
111
|
+
if (details?.consumerAccount) {
|
|
112
|
+
payment.iban = details.consumerAccount
|
|
113
|
+
}
|
|
114
|
+
if (details?.consumerName) {
|
|
115
|
+
payment.ibanName = details.consumerName
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (mollieData.status == "paid") {
|
|
119
|
+
payment.status = PaymentStatus.Succeeded
|
|
120
|
+
payment.paidAt = new Date()
|
|
121
|
+
await payment.save();
|
|
122
|
+
|
|
123
|
+
await invoice.markPaid()
|
|
124
|
+
|
|
125
|
+
// Save customer id
|
|
126
|
+
if (mollieData.customerId && _invoice.organizationId) {
|
|
127
|
+
const organization = await Organization.getByID(_invoice.organizationId)
|
|
128
|
+
if (organization) {
|
|
129
|
+
organization.serverMeta.mollieCustomerId = mollieData.customerId
|
|
130
|
+
console.log("Saving mollie customer", mollieData.customerId, "for organization", organization.id)
|
|
131
|
+
await organization.save()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else if (mollieData.status == "failed" || mollieData.status == "expired" || mollieData.status == "canceled") {
|
|
135
|
+
payment.status = PaymentStatus.Failed
|
|
136
|
+
await payment.save();
|
|
137
|
+
await invoice.markFailed(payment)
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
console.error("Mollie api key is missing for Stamhoofd payments! "+payment.id)
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
console.error("Couldn't find mollie payment for payment "+payment.id)
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
console.error("Payment method not supported for invoice "+invoice.id+" and payment "+payment.id)
|
|
147
|
+
throw new Error("Unsupported payment method for invoices")
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return invoice
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
|
|
2
|
+
import { AnyDecoder, AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { Organization, StripeAccount, StripeCheckoutSession, StripePaymentIntent } from '@stamhoofd/models';
|
|
6
|
+
|
|
7
|
+
import { StripeHelper } from '../../../helpers/StripeHelper';
|
|
8
|
+
import { ExchangePaymentEndpoint } from '../../organization/shared/ExchangePaymentEndpoint';
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
class Body extends AutoEncoder {
|
|
12
|
+
/**
|
|
13
|
+
* The account id (internal id, not the stripe id)
|
|
14
|
+
*/
|
|
15
|
+
@field({ decoder: StringDecoder })
|
|
16
|
+
type: string
|
|
17
|
+
|
|
18
|
+
@field({ decoder: StringDecoder })
|
|
19
|
+
id: string
|
|
20
|
+
|
|
21
|
+
@field({ decoder: AnyDecoder })
|
|
22
|
+
data: any
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Query = undefined
|
|
26
|
+
type ResponseBody = undefined
|
|
27
|
+
|
|
28
|
+
export class StripeWebookEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
29
|
+
bodyDecoder = Body as Decoder<Body>
|
|
30
|
+
|
|
31
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
32
|
+
if (request.method !== "POST") {
|
|
33
|
+
return [false];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const params = Endpoint.parseParameters(request.url, "/stripe/webhooks", {});
|
|
37
|
+
|
|
38
|
+
if (params) {
|
|
39
|
+
return [true, params as Params];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return [false];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
46
|
+
console.log("Received Stripe Webhook", request.body.type);
|
|
47
|
+
|
|
48
|
+
// Verify webhook signature and extract the event.
|
|
49
|
+
// See https://stripe.com/docs/webhooks/signatures for more information.
|
|
50
|
+
let event;
|
|
51
|
+
try {
|
|
52
|
+
const stripe = StripeHelper.getInstance()
|
|
53
|
+
const sig = request.headers['stripe-signature'];
|
|
54
|
+
if (!sig) {
|
|
55
|
+
throw new SimpleError({
|
|
56
|
+
code: "invalid_signature",
|
|
57
|
+
message: "Invalid signature",
|
|
58
|
+
statusCode: 400
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
event = await stripe.webhooks.constructEventAsync(await request.request.bodyPromise!, sig, STAMHOOFD.STRIPE_ENDPOINT_SECRET);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw new SimpleError({
|
|
64
|
+
code: "invalid_signature",
|
|
65
|
+
message: "Invalid signature",
|
|
66
|
+
statusCode: 400
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check type
|
|
71
|
+
switch (request.body.type) {
|
|
72
|
+
case "account.updated": {
|
|
73
|
+
console.log(event);
|
|
74
|
+
const account = request.body.data.object;
|
|
75
|
+
if (account && account.id) {
|
|
76
|
+
const id = account.id as string;
|
|
77
|
+
const [model] = await StripeAccount.where({accountId: id}, {limit: 1});
|
|
78
|
+
if (model) {
|
|
79
|
+
model.setMetaFromStripeAccount(account)
|
|
80
|
+
await model.save()
|
|
81
|
+
} else {
|
|
82
|
+
console.warn("Could not find stripe account with id", id)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "payment_intent.amount_capturable_updated":
|
|
88
|
+
case "payment_intent.canceled":
|
|
89
|
+
case "payment_intent.created":
|
|
90
|
+
case "payment_intent.partially_funded":
|
|
91
|
+
case "payment_intent.payment_failed":
|
|
92
|
+
case "payment_intent.processing":
|
|
93
|
+
case "payment_intent.requires_action":
|
|
94
|
+
case "payment_intent.succeeded": {
|
|
95
|
+
const intentId = request.body.data.object.id;
|
|
96
|
+
const [model] = await StripePaymentIntent.where({stripeIntentId: intentId}, {limit: 1})
|
|
97
|
+
if (model && model.organizationId) {
|
|
98
|
+
const organization = await Organization.getByID(model.organizationId)
|
|
99
|
+
if (organization) {
|
|
100
|
+
await ExchangePaymentEndpoint.pollStatus(model.paymentId, organization)
|
|
101
|
+
} else {
|
|
102
|
+
console.warn("Could not find organization with id", model.organizationId)
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
console.warn("Could not find stripe payment intent with id", intentId)
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "checkout.session.async_payment_failed":
|
|
110
|
+
case "checkout.session.async_payment_succeeded":
|
|
111
|
+
case "checkout.session.completed":
|
|
112
|
+
case "checkout.session.expired": {
|
|
113
|
+
const checkoutId = request.body.data.object.id;
|
|
114
|
+
const [model] = await StripeCheckoutSession.where({stripeSessionId: checkoutId}, {limit: 1})
|
|
115
|
+
if (model && model.organizationId) {
|
|
116
|
+
const organization = await Organization.getByID(model.organizationId)
|
|
117
|
+
if (organization) {
|
|
118
|
+
await ExchangePaymentEndpoint.pollStatus(model.paymentId, organization)
|
|
119
|
+
} else {
|
|
120
|
+
console.warn("Could not find organization with id", model.organizationId)
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
console.warn("Could not find stripe checkout session with id", checkoutId)
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
default: {
|
|
128
|
+
console.log("Unhandled stripe webhook type", request.body.type);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return new Response(undefined);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { User } from '@stamhoofd/models';
|
|
3
|
+
import { User as UserStruct } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { Context } from "../../../helpers/Context";
|
|
6
|
+
type Params = Record<string, never>;
|
|
7
|
+
type Query = undefined;
|
|
8
|
+
type Body = undefined
|
|
9
|
+
type ResponseBody = UserStruct[]
|
|
10
|
+
|
|
11
|
+
export class GetPlatformAdminsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
12
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
13
|
+
if (request.method != "GET") {
|
|
14
|
+
return [false];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const params = Endpoint.parseParameters(request.url, "/platform/admins", {});
|
|
18
|
+
|
|
19
|
+
if (params) {
|
|
20
|
+
return [true, params as Params];
|
|
21
|
+
}
|
|
22
|
+
return [false];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
26
|
+
await Context.authenticate()
|
|
27
|
+
|
|
28
|
+
// Fast throw first (more in depth checking for patches later)
|
|
29
|
+
if (!Context.auth.canManagePlatformAdmins()) {
|
|
30
|
+
throw Context.auth.error()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get all admins
|
|
34
|
+
let admins = await User.where({ organizationId: null, permissions: { sign: "!=", value: null }})
|
|
35
|
+
|
|
36
|
+
// Hide api accounts
|
|
37
|
+
admins = admins.filter(a => !a.isApiUser)
|
|
38
|
+
admins = admins.filter(a => !!a.permissions?.globalPermissions)
|
|
39
|
+
|
|
40
|
+
return new Response(
|
|
41
|
+
admins.map(a => UserStruct.create({...a, hasAccount: a.hasAccount()}))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { Platform } from "@stamhoofd/models";
|
|
3
|
+
import { Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { Context } from "../../../helpers/Context";
|
|
6
|
+
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = undefined;
|
|
9
|
+
type Body = undefined
|
|
10
|
+
type ResponseBody = PlatformStruct;
|
|
11
|
+
|
|
12
|
+
export class GetPlatformEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != "GET") {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, "/platform", {});
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
await Context.optionalAuthenticate({allowWithoutAccount: false})
|
|
28
|
+
|
|
29
|
+
if (Context.optionalAuth?.hasSomePlatformAccess()) {
|
|
30
|
+
const platform = await Platform.getSharedPrivateStruct()
|
|
31
|
+
if (!platform.privateConfig) {
|
|
32
|
+
throw new Error("Private config not found")
|
|
33
|
+
}
|
|
34
|
+
return new Response(platform);
|
|
35
|
+
}
|
|
36
|
+
const platform = await Platform.getSharedStruct()
|
|
37
|
+
return new Response(platform);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AutoEncoderPatchType, Decoder, patchObject } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { Platform } from "@stamhoofd/models";
|
|
4
|
+
import { Platform as PlatformStruct } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { Context } from "../../../helpers/Context";
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = undefined;
|
|
10
|
+
type Body = AutoEncoderPatchType<PlatformStruct>;
|
|
11
|
+
type ResponseBody = PlatformStruct;
|
|
12
|
+
|
|
13
|
+
export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
bodyDecoder = PlatformStruct.patchType() as Decoder<AutoEncoderPatchType<PlatformStruct>>
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "PATCH") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/platform", {});
|
|
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.authenticate()
|
|
31
|
+
|
|
32
|
+
// Fast throw first (more in depth checking for patches later)
|
|
33
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
34
|
+
throw Context.auth.error()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const platform = await Platform.getShared()
|
|
38
|
+
|
|
39
|
+
if (request.body.privateConfig) {
|
|
40
|
+
// Did we patch roles?
|
|
41
|
+
if (request.body.privateConfig.roles) {
|
|
42
|
+
if (!Context.auth.canManagePlatformAdmins()) {
|
|
43
|
+
throw Context.auth.error()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Update roles
|
|
47
|
+
platform.privateConfig.roles = patchObject(platform.privateConfig.roles, request.body.privateConfig.roles)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (request.body.config) {
|
|
52
|
+
if (!Context.auth.hasPlatformFullAccess()) {
|
|
53
|
+
throw Context.auth.error()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update config
|
|
57
|
+
platform.config = patchObject(platform.config, request.body.config)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await platform.save()
|
|
61
|
+
return new Response(await Platform.getSharedPrivateStruct());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ManyToOneRelation } from "@simonbackx/simple-database";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
4
|
+
import { Group, Member, Payment, Registration } from '@stamhoofd/models';
|
|
5
|
+
import { PaymentWithRegistrations } from "@stamhoofd/structures";
|
|
6
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
7
|
+
|
|
8
|
+
import { Context } from "../../../helpers/Context";
|
|
9
|
+
type Params = {id: string};
|
|
10
|
+
type Query = undefined
|
|
11
|
+
type Body = undefined
|
|
12
|
+
type ResponseBody = PaymentWithRegistrations | undefined;
|
|
13
|
+
|
|
14
|
+
export class GetPaymentRegistrations extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
16
|
+
if (request.method != "GET") {
|
|
17
|
+
return [false];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const params = Endpoint.parseParameters(request.url, "/payments/@id/registrations", {id: String});
|
|
21
|
+
|
|
22
|
+
if (params) {
|
|
23
|
+
return [true, params as Params];
|
|
24
|
+
}
|
|
25
|
+
return [false];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
29
|
+
await Context.setUserOrganizationScope();
|
|
30
|
+
const {user} = await Context.authenticate()
|
|
31
|
+
|
|
32
|
+
const payment = await Payment.getByID(request.params.id)
|
|
33
|
+
if (!payment) {
|
|
34
|
+
throw new SimpleError({
|
|
35
|
+
code: "",
|
|
36
|
+
message: "Deze link is ongeldig"
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
const registrations = await Member.getRegistrationWithMembersForPayment(payment.id)
|
|
40
|
+
const authorizedMembers = (await Member.getMembersWithRegistrationForUser(user)).map(m => m.id)
|
|
41
|
+
const groups = await Group.getByIDs(...Formatter.uniqueArray(registrations.map(r => r.groupId)))
|
|
42
|
+
|
|
43
|
+
// Check permissions
|
|
44
|
+
for (const registration of registrations) {
|
|
45
|
+
if (!authorizedMembers.includes(registration.member.id)) {
|
|
46
|
+
throw new SimpleError({
|
|
47
|
+
code: "",
|
|
48
|
+
message: "Deze link is ongeldig"
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Response(
|
|
54
|
+
PaymentWithRegistrations.create({
|
|
55
|
+
id: payment.id,
|
|
56
|
+
method: payment.method,
|
|
57
|
+
status: payment.status,
|
|
58
|
+
price: payment.price,
|
|
59
|
+
freeContribution: payment.freeContribution,
|
|
60
|
+
transferDescription: payment.transferDescription,
|
|
61
|
+
paidAt: payment.paidAt,
|
|
62
|
+
createdAt: payment.createdAt,
|
|
63
|
+
updatedAt: payment.updatedAt,
|
|
64
|
+
registrations: registrations.map(r => Member.getRegistrationWithMemberStructure(r.setRelation(Registration.group, groups.find(g => g.id === r.groupId)!)))
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { BalanceItem, Member } from "@stamhoofd/models";
|
|
3
|
+
import { MemberBalanceItem } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { Context } from "../../../helpers/Context";
|
|
6
|
+
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = undefined
|
|
9
|
+
type Body = undefined
|
|
10
|
+
type ResponseBody = MemberBalanceItem[]
|
|
11
|
+
|
|
12
|
+
export class GetUserBalanceEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != "GET") {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, "/balance", {});
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
const organization = await Context.setUserOrganizationScope();
|
|
28
|
+
const {user} = await Context.authenticate()
|
|
29
|
+
|
|
30
|
+
const members = await Member.getMembersWithRegistrationForUser(user)
|
|
31
|
+
|
|
32
|
+
// Get all balance items for this member or users
|
|
33
|
+
const balanceItems = await BalanceItem.balanceItemsForUsersAndMembers(organization?.id ?? null, [user.id], members.map(m => m.id))
|
|
34
|
+
|
|
35
|
+
return new Response(
|
|
36
|
+
await BalanceItem.getMemberStructure(balanceItems)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { Document, DocumentTemplate, Member } from '@stamhoofd/models';
|
|
3
|
+
import { Document as DocumentStruct,DocumentStatus } from "@stamhoofd/structures";
|
|
4
|
+
import { Sorter } from "@stamhoofd/utility";
|
|
5
|
+
|
|
6
|
+
import { Context } from "../../../helpers/Context";
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = undefined;
|
|
9
|
+
type Body = undefined
|
|
10
|
+
type ResponseBody = DocumentStruct[];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the members of the user
|
|
14
|
+
*/
|
|
15
|
+
export class GetUserMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
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, "/documents", {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
31
|
+
const organization = await Context.setUserOrganizationScope();
|
|
32
|
+
const {user} = await Context.authenticate()
|
|
33
|
+
|
|
34
|
+
const members = await Member.getMembersWithRegistrationForUser(user)
|
|
35
|
+
let templates = organization ? await DocumentTemplate.where({ status: 'Published', organizationId: organization.id }) : null
|
|
36
|
+
const memberIds = members.map(m => m.id)
|
|
37
|
+
const templateIds = templates ? templates.map(t => t.id) : null
|
|
38
|
+
|
|
39
|
+
if (memberIds.length == 0 || (templateIds !== null && templateIds.length == 0)) {
|
|
40
|
+
return new Response([]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const w: any = {
|
|
44
|
+
memberId: {
|
|
45
|
+
sign: 'IN',
|
|
46
|
+
value: memberIds
|
|
47
|
+
},
|
|
48
|
+
status: {
|
|
49
|
+
sign: 'IN',
|
|
50
|
+
value: [DocumentStatus.MissingData, DocumentStatus.Published]
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (templateIds !== null) {
|
|
55
|
+
w.templateId = {
|
|
56
|
+
sign: 'IN',
|
|
57
|
+
value: templateIds
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
62
|
+
const documents = await Document.where(w);
|
|
63
|
+
|
|
64
|
+
if (!templates) {
|
|
65
|
+
templates = documents.length > 0 ? await DocumentTemplate.getByIDs(...documents.map(d => d.templateId)) : []
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const filteredDocuments = documents.filter(document => {
|
|
69
|
+
const template = templates.find(t => t.id == document.templateId)
|
|
70
|
+
if (!template || (!template.updatesEnabled && document.status === DocumentStatus.MissingData)) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
filteredDocuments.sort((a, b) => Sorter.byDateValue(a.createdAt, b.createdAt))
|
|
77
|
+
|
|
78
|
+
return new Response(filteredDocuments.map(d => d.getStructure()));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { Member } from '@stamhoofd/models';
|
|
3
|
+
import { MembersBlob } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
|
|
6
|
+
import { Context } from "../../../helpers/Context";
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = undefined;
|
|
10
|
+
type Body = undefined
|
|
11
|
+
type ResponseBody = MembersBlob
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the members of the user
|
|
15
|
+
*/
|
|
16
|
+
export class GetUserMembersEndpoint 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, "/user/members", {});
|
|
23
|
+
|
|
24
|
+
if (params) {
|
|
25
|
+
return [true, params as Params];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return [false];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
32
|
+
await Context.setUserOrganizationScope();
|
|
33
|
+
const {user} = await Context.authenticate()
|
|
34
|
+
|
|
35
|
+
const members = await Member.getMembersWithRegistrationForUser(user)
|
|
36
|
+
|
|
37
|
+
return new Response(
|
|
38
|
+
await AuthenticatedStructures.membersBlob(members)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|