@stamhoofd/backend 2.19.0 → 2.21.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 +1 -1
- package/package.json +6 -6
- package/src/crons.ts +3 -67
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +33 -29
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +3 -8
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +10 -1
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +0 -47
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +14 -16
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +8 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +1 -1
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +171 -36
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +2 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +1 -1
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +5 -2
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +28 -32
- package/src/helpers/AdminPermissionChecker.ts +22 -5
- package/src/helpers/AuthenticatedStructures.ts +3 -2
- package/src/helpers/SetupStepsUpdater.ts +115 -13
- package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +0 -153
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +0 -64
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +0 -84
- package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +0 -65
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Group,
|
|
3
|
+
MemberResponsibilityRecord,
|
|
2
4
|
Organization,
|
|
3
5
|
OrganizationRegistrationPeriod,
|
|
4
|
-
Platform
|
|
6
|
+
Platform
|
|
5
7
|
} from "@stamhoofd/models";
|
|
6
8
|
import { QueueHandler } from "@stamhoofd/queues";
|
|
9
|
+
import { SQL, SQLWhereSign } from "@stamhoofd/sql";
|
|
7
10
|
import {
|
|
8
|
-
|
|
11
|
+
MemberResponsibility,
|
|
9
12
|
Platform as PlatformStruct,
|
|
10
13
|
SetupStepType,
|
|
11
|
-
SetupSteps
|
|
14
|
+
SetupSteps
|
|
12
15
|
} from "@stamhoofd/structures";
|
|
13
16
|
|
|
14
|
-
type SetupStepOperation = (setupSteps: SetupSteps, organization: Organization, platform: PlatformStruct) => void
|
|
17
|
+
type SetupStepOperation = (setupSteps: SetupSteps, organization: Organization, platform: PlatformStruct) => void | Promise<void>;
|
|
15
18
|
|
|
16
19
|
export class SetupStepUpdater {
|
|
17
20
|
private static readonly STEP_TYPE_OPERATIONS: Record<
|
|
18
21
|
SetupStepType,
|
|
19
22
|
SetupStepOperation
|
|
20
23
|
> = {
|
|
24
|
+
[SetupStepType.Functions]: this.updateStepFunctions,
|
|
25
|
+
[SetupStepType.Companies]: this.updateStepCompanies,
|
|
21
26
|
[SetupStepType.Groups]: this.updateStepGroups,
|
|
22
27
|
[SetupStepType.Premises]: this.updateStepPremises,
|
|
23
28
|
};
|
|
24
29
|
|
|
25
30
|
static async updateSetupStepsForAllOrganizationsInCurrentPeriod({
|
|
26
|
-
batchSize
|
|
27
|
-
}: { batchSize?: number
|
|
31
|
+
batchSize
|
|
32
|
+
}: { batchSize?: number } = {}) {
|
|
28
33
|
const tag = "updateSetupStepsForAllOrganizationsInCurrentPeriod";
|
|
29
34
|
QueueHandler.cancel(tag);
|
|
30
35
|
|
|
31
36
|
await QueueHandler.schedule(tag, async () => {
|
|
32
37
|
const platform = (await Platform.getSharedPrivateStruct()).clone();
|
|
33
|
-
|
|
34
|
-
platform.config.premiseTypes = premiseTypes;
|
|
35
|
-
}
|
|
38
|
+
|
|
36
39
|
const periodId = platform.period.id;
|
|
37
40
|
|
|
38
41
|
let lastId = "";
|
|
@@ -136,7 +139,7 @@ export class SetupStepUpdater {
|
|
|
136
139
|
);
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
static async updateFor(
|
|
142
|
+
private static async updateFor(
|
|
140
143
|
organizationRegistrationPeriod: OrganizationRegistrationPeriod,
|
|
141
144
|
platform: PlatformStruct,
|
|
142
145
|
organization: Organization
|
|
@@ -147,13 +150,13 @@ export class SetupStepUpdater {
|
|
|
147
150
|
for (const stepType of Object.values(SetupStepType)) {
|
|
148
151
|
console.log(`[STEP TYPE] ${stepType}`);
|
|
149
152
|
const operation = this.STEP_TYPE_OPERATIONS[stepType];
|
|
150
|
-
operation(setupSteps, organization, platform);
|
|
153
|
+
await operation(setupSteps, organization, platform);
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
await organizationRegistrationPeriod.save();
|
|
154
157
|
}
|
|
155
158
|
|
|
156
|
-
static updateStepPremises(
|
|
159
|
+
private static updateStepPremises(
|
|
157
160
|
setupSteps: SetupSteps,
|
|
158
161
|
organization: Organization,
|
|
159
162
|
platform: PlatformStruct
|
|
@@ -165,6 +168,8 @@ export class SetupStepUpdater {
|
|
|
165
168
|
|
|
166
169
|
for (const premiseType of premiseTypes) {
|
|
167
170
|
const { min, max } = premiseType;
|
|
171
|
+
|
|
172
|
+
// only add step if premise type has restrictions
|
|
168
173
|
if (min === null && max === null) {
|
|
169
174
|
continue;
|
|
170
175
|
}
|
|
@@ -197,7 +202,7 @@ export class SetupStepUpdater {
|
|
|
197
202
|
});
|
|
198
203
|
}
|
|
199
204
|
|
|
200
|
-
static updateStepGroups(
|
|
205
|
+
private static updateStepGroups(
|
|
201
206
|
setupSteps: SetupSteps,
|
|
202
207
|
_organization: Organization,
|
|
203
208
|
_platform: PlatformStruct
|
|
@@ -207,4 +212,101 @@ export class SetupStepUpdater {
|
|
|
207
212
|
finishedSteps: 0,
|
|
208
213
|
});
|
|
209
214
|
}
|
|
215
|
+
|
|
216
|
+
private static updateStepCompanies(
|
|
217
|
+
setupSteps: SetupSteps,
|
|
218
|
+
_organization: Organization,
|
|
219
|
+
_platform: PlatformStruct
|
|
220
|
+
) {
|
|
221
|
+
setupSteps.update(SetupStepType.Companies, {
|
|
222
|
+
totalSteps: 0,
|
|
223
|
+
finishedSteps: 0,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static async updateStepFunctions(
|
|
228
|
+
setupSteps: SetupSteps,
|
|
229
|
+
organization: Organization,
|
|
230
|
+
platform: PlatformStruct
|
|
231
|
+
) {
|
|
232
|
+
const now = new Date();
|
|
233
|
+
const organizationBasedResponsibilitiesWithRestriction = platform.config.responsibilities
|
|
234
|
+
.filter(r => r.organizationBased && (r.minimumMembers || r.maximumMembers));
|
|
235
|
+
|
|
236
|
+
const responsibilityIds = organizationBasedResponsibilitiesWithRestriction.map(r => r.id);
|
|
237
|
+
|
|
238
|
+
const records = await MemberResponsibilityRecord.select()
|
|
239
|
+
.where('responsibilityId', responsibilityIds)
|
|
240
|
+
.where('organizationId', organization.id)
|
|
241
|
+
.where(SQL.where('endDate', SQLWhereSign.Greater, now).or('endDate', null))
|
|
242
|
+
.fetch();
|
|
243
|
+
|
|
244
|
+
let totalSteps = 0;
|
|
245
|
+
let finishedSteps = 0;
|
|
246
|
+
|
|
247
|
+
const groups = await Group.getAll(organization.id, organization.periodId);
|
|
248
|
+
|
|
249
|
+
const flatResponsibilities: {responsibility: MemberResponsibility, group: Group | null}[] = organizationBasedResponsibilitiesWithRestriction
|
|
250
|
+
.flatMap(responsibility => {
|
|
251
|
+
const defaultAgeGroupIds = responsibility.defaultAgeGroupIds;
|
|
252
|
+
if(defaultAgeGroupIds === null) {
|
|
253
|
+
const item: {responsibility: MemberResponsibility, group: Group | null} = {
|
|
254
|
+
responsibility,
|
|
255
|
+
group: null
|
|
256
|
+
}
|
|
257
|
+
return [item];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return groups
|
|
261
|
+
.filter(g => g.defaultAgeGroupId !== null && defaultAgeGroupIds.includes(g.defaultAgeGroupId))
|
|
262
|
+
.map(group => {
|
|
263
|
+
return {
|
|
264
|
+
responsibility,
|
|
265
|
+
group
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
for(const {responsibility, group} of flatResponsibilities) {
|
|
271
|
+
const { minimumMembers: min, maximumMembers: max } = responsibility;
|
|
272
|
+
|
|
273
|
+
if (min === null && max === null) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
totalSteps++;
|
|
278
|
+
|
|
279
|
+
const responsibilityId = responsibility.id;
|
|
280
|
+
let totalRecordsWithThisResponsibility = 0;
|
|
281
|
+
|
|
282
|
+
if(group === null) {
|
|
283
|
+
for (const record of records) {
|
|
284
|
+
if (record.responsibilityId === responsibilityId) {
|
|
285
|
+
totalRecordsWithThisResponsibility++;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
for (const record of records) {
|
|
290
|
+
if (record.responsibilityId === responsibilityId && record.groupId === group.id) {
|
|
291
|
+
totalRecordsWithThisResponsibility++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (max !== null && totalRecordsWithThisResponsibility > max) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (min !== null && totalRecordsWithThisResponsibility < min) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
finishedSteps++;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
setupSteps.update(SetupStepType.Functions, {
|
|
308
|
+
totalSteps,
|
|
309
|
+
finishedSteps,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
210
312
|
}
|
|
@@ -1,153 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { Request } from "@simonbackx/simple-endpoints";
|
|
3
|
-
import { OrganizationFactory, RegisterCodeFactory, STCredit, Token, UserFactory } from "@stamhoofd/models";
|
|
4
|
-
import { PermissionLevel, Permissions } from "@stamhoofd/structures";
|
|
5
|
-
|
|
6
|
-
import { testServer } from "../../../../../tests/helpers/TestServer";
|
|
7
|
-
import { ApplyRegisterCodeEndpoint } from "./ApplyRegisterCodeEndpoint";
|
|
8
|
-
|
|
9
|
-
describe("Endpoint.ApplyRegisterCodeEndpoint", () => {
|
|
10
|
-
// Test endpoint
|
|
11
|
-
const endpoint = new ApplyRegisterCodeEndpoint();
|
|
12
|
-
|
|
13
|
-
test("Cannot apply a register code if not platform admin", async () => {
|
|
14
|
-
const otherOrganization = await new OrganizationFactory({}).create();
|
|
15
|
-
const code = await new RegisterCodeFactory({organization: otherOrganization}).create();
|
|
16
|
-
|
|
17
|
-
const organization = await new OrganizationFactory({}).create();
|
|
18
|
-
const user = await new UserFactory({ organization, permissions: Permissions.create({ level: PermissionLevel.Full }) }).create()
|
|
19
|
-
const token = await Token.createToken(user)
|
|
20
|
-
|
|
21
|
-
const r = Request.buildJson(
|
|
22
|
-
"POST",
|
|
23
|
-
"/organization/register-code",
|
|
24
|
-
organization.getApiHost(),
|
|
25
|
-
{
|
|
26
|
-
registerCode: code.code,
|
|
27
|
-
}
|
|
28
|
-
);
|
|
29
|
-
r.headers.authorization = "Bearer "+token.accessToken
|
|
30
|
-
|
|
31
|
-
await expect(testServer.test(endpoint, r)).rejects.toThrow("You do not have permissions for this action");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("Can apply a register code and apply the discount", async () => {
|
|
35
|
-
const otherOrganization = await new OrganizationFactory({}).create();
|
|
36
|
-
const code = await new RegisterCodeFactory({organization: otherOrganization}).create();
|
|
37
|
-
|
|
38
|
-
const organization = await new OrganizationFactory({}).create();
|
|
39
|
-
const user = await new UserFactory({
|
|
40
|
-
organization,
|
|
41
|
-
globalPermissions: Permissions.create({ level: PermissionLevel.Full }),
|
|
42
|
-
email: 'admin@stamhoofd.be'
|
|
43
|
-
}).create()
|
|
44
|
-
const token = await Token.createToken(user)
|
|
45
|
-
|
|
46
|
-
const r = Request.buildJson(
|
|
47
|
-
"POST",
|
|
48
|
-
"/organization/register-code",
|
|
49
|
-
organization.getApiHost(),
|
|
50
|
-
{
|
|
51
|
-
registerCode: code.code,
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
r.headers.authorization = "Bearer "+token.accessToken
|
|
55
|
-
|
|
56
|
-
const response = await testServer.test(endpoint, r);
|
|
57
|
-
expect(response.body).toBeUndefined();
|
|
58
|
-
|
|
59
|
-
// Check if this organization has an open register code
|
|
60
|
-
const credits = await STCredit.getForOrganization(organization.id);
|
|
61
|
-
expect(credits.length).toBe(1);
|
|
62
|
-
expect(credits[0].change).toBe(code.value);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
|
-
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
-
import { Email } from '@stamhoofd/email';
|
|
4
|
-
import { RegisterCode, UsedRegisterCode } from '@stamhoofd/models';
|
|
5
|
-
|
|
6
|
-
import { Context } from '../../../../helpers/Context';
|
|
7
|
-
|
|
8
|
-
type Params = Record<string, never>;
|
|
9
|
-
type Query = undefined;
|
|
10
|
-
type ResponseBody = undefined;
|
|
11
|
-
|
|
12
|
-
class Body extends AutoEncoder {
|
|
13
|
-
@field({ decoder: StringDecoder })
|
|
14
|
-
registerCode: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 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
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
export class ApplyRegisterCodeEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
22
|
-
bodyDecoder = Body as Decoder<Body>
|
|
23
|
-
|
|
24
|
-
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
25
|
-
if (request.method != "POST") {
|
|
26
|
-
return [false];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const params = Endpoint.parseParameters(request.url, "/organization/register-code", {});
|
|
30
|
-
|
|
31
|
-
if (params) {
|
|
32
|
-
return [true, params as Params];
|
|
33
|
-
}
|
|
34
|
-
return [false];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
38
|
-
const organization = await Context.setOrganizationScope();
|
|
39
|
-
await Context.authenticate()
|
|
40
|
-
|
|
41
|
-
if (!Context.auth.hasPlatformFullAccess()) {
|
|
42
|
-
throw Context.auth.error()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let code = request.body.registerCode;
|
|
46
|
-
|
|
47
|
-
if (code.startsWith('https:')) {
|
|
48
|
-
try {
|
|
49
|
-
const url = new URL(code);
|
|
50
|
-
const codeParam = url.searchParams.get('code');
|
|
51
|
-
if (codeParam) {
|
|
52
|
-
console.log('Parsed code from URL', codeParam)
|
|
53
|
-
code = codeParam;
|
|
54
|
-
}
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.error('Tried parsing code as URL but failed', code)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const {models, emails} = await RegisterCode.applyRegisterCode(organization, code)
|
|
61
|
-
|
|
62
|
-
for (const model of models) {
|
|
63
|
-
await model.save();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const email of emails) {
|
|
67
|
-
Email.sendInternal(email, organization.i18n)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (organization.meta.packages.isPaid) {
|
|
71
|
-
// Already bought something: apply credit to other organization immediately
|
|
72
|
-
const code = await UsedRegisterCode.getFor(organization.id)
|
|
73
|
-
if (code && !code.creditId) {
|
|
74
|
-
console.log("Rewarding code "+code.id+" for payment")
|
|
75
|
-
|
|
76
|
-
// Deze code werd nog niet beloond
|
|
77
|
-
await code.reward()
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return new Response(undefined);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import { Organization, RegisterCode, STCredit, UsedRegisterCode } from '@stamhoofd/models';
|
|
3
|
-
import { RegisterCodeStatus, UsedRegisterCode as UsedRegisterCodeStruct } 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 = RegisterCodeStatus;
|
|
11
|
-
|
|
12
|
-
export class GetRegisterCodeEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
-
|
|
14
|
-
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
15
|
-
if (request.method != "GET") {
|
|
16
|
-
return [false];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const params = Endpoint.parseParameters(request.url, "/register-code", {});
|
|
20
|
-
|
|
21
|
-
if (params) {
|
|
22
|
-
return [true, params as Params];
|
|
23
|
-
}
|
|
24
|
-
return [false];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
28
|
-
const organization = await Context.setOrganizationScope();
|
|
29
|
-
await Context.authenticate()
|
|
30
|
-
|
|
31
|
-
if (!await Context.auth.hasSomeAccess(organization.id)) {
|
|
32
|
-
throw Context.auth.error()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const codes = await RegisterCode.where({ organizationId: organization.id })
|
|
36
|
-
let code = codes[0]
|
|
37
|
-
|
|
38
|
-
if (codes.length == 0) {
|
|
39
|
-
code = new RegisterCode()
|
|
40
|
-
code.organizationId = organization.id
|
|
41
|
-
code.description = "Doorverwezen door "+ organization.name
|
|
42
|
-
code.value = 2500
|
|
43
|
-
await code.generateCode()
|
|
44
|
-
await code.save()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const usedCodes = await UsedRegisterCode.getAll(code.code)
|
|
48
|
-
const allOrganizations = await Organization.getByIDs(...usedCodes.flatMap(u => u.organizationId ? [u.organizationId] : []))
|
|
49
|
-
const allCredits = await STCredit.getByIDs(...usedCodes.flatMap(u => u.creditId ? [u.creditId] : []))
|
|
50
|
-
|
|
51
|
-
return new Response(RegisterCodeStatus.create({
|
|
52
|
-
code: code.code,
|
|
53
|
-
value: code.value,
|
|
54
|
-
invoiceValue: code.invoiceValue,
|
|
55
|
-
usedCodes: usedCodes.map(c => {
|
|
56
|
-
return UsedRegisterCodeStruct.create({
|
|
57
|
-
id: c.id,
|
|
58
|
-
organizationName: allOrganizations.find(o => o.id === c.organizationId)?.name ?? "Onbekend",
|
|
59
|
-
createdAt: c.createdAt,
|
|
60
|
-
creditValue: (c.creditId ? allCredits.find(credit => credit.id === c.creditId)?.change : null) ?? null
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
}))
|
|
64
|
-
}
|
|
65
|
-
}
|