@stamhoofd/backend 2.83.5 → 2.84.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/index.ts +19 -4
- package/package.json +18 -14
- package/src/crons/amazon-ses.ts +26 -5
- package/src/crons/balance-emails.ts +18 -17
- package/src/email-recipient-loaders/registrations.ts +87 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +5 -2
- package/src/endpoints/global/email/PatchEmailEndpoint.test.ts +40 -40
- package/src/endpoints/global/events/PatchEventNotificationsEndpoint.test.ts +28 -22
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +81 -49
- package/src/endpoints/global/files/UploadFile.ts +11 -16
- package/src/endpoints/global/groups/GetGroupsEndpoint.test.ts +234 -0
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +117 -43
- package/src/endpoints/global/members/GetMembersEndpoint.test.ts +1054 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +163 -141
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +6 -6
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +0 -16
- package/src/endpoints/global/members/helpers/validateGroupFilter.ts +73 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +1 -2
- package/src/endpoints/global/registration/GetRegistrationsCountEndpoint.ts +43 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.test.ts +1016 -0
- package/src/endpoints/global/registration/GetRegistrationsEndpoint.ts +234 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +5 -5
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +474 -554
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +191 -52
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +107 -9
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.test.ts +89 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +9 -6
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.test.ts +88 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +0 -6
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +10 -6
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +10 -25
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +0 -5
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalanceEndpoint.ts +4 -0
- package/src/endpoints/organization/dashboard/receivable-balances/GetReceivableBalancesEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.test.ts +44 -19
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +140 -25
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +40 -10
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/users/PatchApiUserEndpoint.test.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +4 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +2 -2
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -2
- package/src/excel-loaders/members.ts +233 -232
- package/src/excel-loaders/payments.ts +1 -1
- package/src/excel-loaders/receivable-balances.ts +1 -1
- package/src/excel-loaders/registrations.ts +153 -0
- package/src/helpers/AdminPermissionChecker.ts +65 -37
- package/src/helpers/AuthenticatedStructures.ts +43 -3
- package/src/helpers/Context.ts +29 -1
- package/src/helpers/GlobalHelper.ts +3 -1
- package/src/helpers/GroupedThrottledQueue.test.ts +219 -0
- package/src/helpers/GroupedThrottledQueue.ts +108 -0
- package/src/helpers/LimitedFilteredRequestHelper.ts +26 -1
- package/src/helpers/MemberCharger.ts +0 -5
- package/src/helpers/MembershipCharger.ts +3 -9
- package/src/helpers/OrganizationCharger.ts +0 -5
- package/src/helpers/ThrottledQueue.test.ts +194 -0
- package/src/helpers/ThrottledQueue.ts +145 -0
- package/src/helpers/XlsxTransformerColumnHelper.ts +44 -1
- package/src/middleware/ContextMiddleware.ts +1 -1
- package/src/seeds/1728928974-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/seeds/1735577912-update-cached-outstanding-balance-from-items.ts +2 -1
- package/src/services/BalanceItemPaymentService.ts +1 -33
- package/src/services/BalanceItemService.ts +167 -48
- package/src/services/FileSignService.ts +18 -13
- package/src/services/MemberRecordStore.ts +28 -19
- package/src/services/PaymentReallocationService.test.ts +25 -14
- package/src/services/PaymentReallocationService.ts +29 -10
- package/src/services/PaymentService.ts +4 -16
- package/src/services/PlatformMembershipService.ts +8 -4
- package/src/services/RegistrationService.ts +66 -2
- package/src/sql-filters/base-registration-filter-compilers.ts +43 -0
- package/src/sql-filters/groups.ts +67 -0
- package/src/sql-filters/members.ts +33 -58
- package/src/sql-filters/organization-registration-periods.ts +8 -0
- package/src/sql-filters/registration-periods.ts +8 -0
- package/src/sql-filters/registrations.ts +11 -22
- package/src/sql-sorters/groups.ts +24 -0
- package/src/sql-sorters/organization-registration-periods.ts +24 -0
- package/src/sql-sorters/registration-periods.ts +47 -0
- package/src/sql-sorters/registrations.ts +77 -0
- package/tests/actions/patchOrganizationMember.ts +27 -0
- package/tests/actions/patchPaymentStatus.ts +45 -0
- package/tests/actions/patchUserMember.ts +27 -0
- package/tests/assertions/assertBalances.ts +49 -0
- package/tests/e2e/api-rate-limits.test.ts +5 -5
- package/tests/e2e/bundle-discounts.test.ts +4060 -0
- package/tests/e2e/charge-members.test.ts +27 -24
- package/tests/e2e/documents.test.ts +398 -0
- package/tests/e2e/register.test.ts +292 -312
- package/tests/helpers/PayconiqMocker.ts +55 -0
- package/tests/init/index.ts +5 -0
- package/tests/init/initAdmin.ts +14 -0
- package/tests/init/initBundleDiscount.ts +47 -0
- package/tests/init/initPayconiq.ts +9 -0
- package/tests/init/initPlatformAdmin.ts +13 -0
- package/tests/init/initStripe.ts +21 -0
- package/tests/jest.setup.ts +29 -0
- package/src/seeds-temporary/1736266448-recall-balance-item-price-paid.ts +0 -70
package/index.ts
CHANGED
|
@@ -16,10 +16,13 @@ import { GlobalHelper } from './src/helpers/GlobalHelper';
|
|
|
16
16
|
import { SetupStepUpdater } from './src/helpers/SetupStepUpdater';
|
|
17
17
|
import { ContextMiddleware } from './src/middleware/ContextMiddleware';
|
|
18
18
|
import { AuditLogService } from './src/services/AuditLogService';
|
|
19
|
+
import { BalanceItemService } from './src/services/BalanceItemService';
|
|
19
20
|
import { DocumentService } from './src/services/DocumentService';
|
|
20
21
|
import { FileSignService } from './src/services/FileSignService';
|
|
21
22
|
import { PlatformMembershipService } from './src/services/PlatformMembershipService';
|
|
22
23
|
import { UniqueUserService } from './src/services/UniqueUserService';
|
|
24
|
+
import { QueueHandler } from '@stamhoofd/queues';
|
|
25
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
23
26
|
|
|
24
27
|
process.on('unhandledRejection', (error: Error) => {
|
|
25
28
|
console.error('unhandledRejection');
|
|
@@ -115,8 +118,10 @@ const start = async () => {
|
|
|
115
118
|
|
|
116
119
|
// Register Email Recipient loaders
|
|
117
120
|
await import('./src/email-recipient-loaders/members');
|
|
121
|
+
await import('./src/email-recipient-loaders/registrations');
|
|
118
122
|
await import('./src/email-recipient-loaders/orders');
|
|
119
123
|
await import('./src/email-recipient-loaders/receivable-balances');
|
|
124
|
+
await import('./src/excel-loaders/registrations');
|
|
120
125
|
|
|
121
126
|
console.log('Opening port...');
|
|
122
127
|
routerServer.listen(STAMHOOFD.PORT ?? 9090);
|
|
@@ -163,12 +168,21 @@ const start = async () => {
|
|
|
163
168
|
console.error(err);
|
|
164
169
|
}
|
|
165
170
|
|
|
171
|
+
await BalanceItemService.flushAll();
|
|
166
172
|
await waitForCrons();
|
|
173
|
+
QueueHandler.abortAll(
|
|
174
|
+
new SimpleError({
|
|
175
|
+
code: 'SHUTDOWN',
|
|
176
|
+
message: 'Shutting down',
|
|
177
|
+
statusCode: 503,
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
await QueueHandler.awaitAll();
|
|
167
181
|
|
|
168
182
|
try {
|
|
169
183
|
while (Email.currentQueue.length > 0) {
|
|
170
|
-
console.log(
|
|
171
|
-
await sleep(
|
|
184
|
+
console.log(`${Email.currentQueue.length} emails still in queue. Waiting 500ms...`);
|
|
185
|
+
await sleep(500);
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
188
|
catch (err) {
|
|
@@ -207,13 +221,14 @@ const start = async () => {
|
|
|
207
221
|
|
|
208
222
|
// Register crons
|
|
209
223
|
await import('./src/crons');
|
|
210
|
-
startCrons();
|
|
211
|
-
seeds().catch(console.error);
|
|
212
224
|
|
|
213
225
|
AuditLogService.listen();
|
|
214
226
|
PlatformMembershipService.listen();
|
|
215
227
|
DocumentService.listen();
|
|
216
228
|
SetupStepUpdater.listen();
|
|
229
|
+
|
|
230
|
+
startCrons();
|
|
231
|
+
seeds().catch(console.error);
|
|
217
232
|
};
|
|
218
233
|
|
|
219
234
|
start().catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.84.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -32,22 +32,26 @@
|
|
|
32
32
|
"sinon": "^18.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
+
"@aws-sdk/client-s3": "3.823.0",
|
|
36
|
+
"@aws-sdk/client-ses": "3.823.0",
|
|
37
|
+
"@aws-sdk/client-sesv2": "3.823.0",
|
|
38
|
+
"@aws-sdk/client-sqs": "3.823.0",
|
|
39
|
+
"@aws-sdk/s3-request-presigner": "3.823.0",
|
|
35
40
|
"@bwip-js/node": "^4.5.1",
|
|
36
41
|
"@mollie/api-client": "3.7.0",
|
|
37
|
-
"@simonbackx/simple-database": "1.
|
|
42
|
+
"@simonbackx/simple-database": "1.32.0",
|
|
38
43
|
"@simonbackx/simple-encoding": "2.22.0",
|
|
39
|
-
"@simonbackx/simple-endpoints": "1.
|
|
44
|
+
"@simonbackx/simple-endpoints": "1.20.1",
|
|
40
45
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
41
|
-
"@stamhoofd/backend-i18n": "2.
|
|
42
|
-
"@stamhoofd/backend-middleware": "2.
|
|
43
|
-
"@stamhoofd/email": "2.
|
|
44
|
-
"@stamhoofd/models": "2.
|
|
45
|
-
"@stamhoofd/queues": "2.
|
|
46
|
-
"@stamhoofd/sql": "2.
|
|
47
|
-
"@stamhoofd/structures": "2.
|
|
48
|
-
"@stamhoofd/utility": "2.
|
|
46
|
+
"@stamhoofd/backend-i18n": "2.84.0",
|
|
47
|
+
"@stamhoofd/backend-middleware": "2.84.0",
|
|
48
|
+
"@stamhoofd/email": "2.84.0",
|
|
49
|
+
"@stamhoofd/models": "2.84.0",
|
|
50
|
+
"@stamhoofd/queues": "2.84.0",
|
|
51
|
+
"@stamhoofd/sql": "2.84.0",
|
|
52
|
+
"@stamhoofd/structures": "2.84.0",
|
|
53
|
+
"@stamhoofd/utility": "2.84.0",
|
|
49
54
|
"archiver": "^7.0.1",
|
|
50
|
-
"aws-sdk": "^2.885.0",
|
|
51
55
|
"axios": "1.6.8",
|
|
52
56
|
"cookie": "^0.5.0",
|
|
53
57
|
"formidable": "3.5.1",
|
|
@@ -56,7 +60,7 @@
|
|
|
56
60
|
"luxon": "3.4.4",
|
|
57
61
|
"mailparser": "3.7.0",
|
|
58
62
|
"mockdate": "^3.0.2",
|
|
59
|
-
"
|
|
63
|
+
"mysql2": "^3.14.1",
|
|
60
64
|
"node-rsa": "1.1.1",
|
|
61
65
|
"openid-client": "^5.4.0",
|
|
62
66
|
"postmark": "^4.0.5",
|
|
@@ -65,5 +69,5 @@
|
|
|
65
69
|
"publishConfig": {
|
|
66
70
|
"access": "public"
|
|
67
71
|
},
|
|
68
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "99cf8650892792358d7516bb83458642d886dee7"
|
|
69
73
|
}
|
package/src/crons/amazon-ses.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
+
import {
|
|
3
|
+
DeleteMessageCommand,
|
|
4
|
+
ReceiveMessageCommand,
|
|
5
|
+
SQSClient,
|
|
6
|
+
} from '@aws-sdk/client-sqs';
|
|
2
7
|
import { registerCron } from '@stamhoofd/crons';
|
|
3
8
|
import { Email, EmailAddress } from '@stamhoofd/email';
|
|
4
9
|
import { AuditLog, Organization } from '@stamhoofd/models';
|
|
5
10
|
import { AuditLogReplacement, AuditLogReplacementType, AuditLogSource, AuditLogType } from '@stamhoofd/structures';
|
|
6
|
-
import AWS from 'aws-sdk';
|
|
7
11
|
import { ForwardHandler } from '../helpers/ForwardHandler';
|
|
8
12
|
|
|
9
13
|
registerCron('checkComplaints', checkComplaints);
|
|
@@ -192,10 +196,25 @@ async function handleForward(message: any) {
|
|
|
192
196
|
}
|
|
193
197
|
}
|
|
194
198
|
|
|
199
|
+
let sharedClient: SQSClient | null = null;
|
|
200
|
+
function getClient() {
|
|
201
|
+
if (!sharedClient) {
|
|
202
|
+
sharedClient = new SQSClient({});
|
|
203
|
+
}
|
|
204
|
+
return sharedClient;
|
|
205
|
+
}
|
|
206
|
+
|
|
195
207
|
async function readFromQueue(queueUrl: string) {
|
|
196
208
|
console.log('[AWS Queue] Checking ' + queueUrl);
|
|
197
|
-
|
|
198
|
-
const
|
|
209
|
+
|
|
210
|
+
const client = getClient();
|
|
211
|
+
|
|
212
|
+
const cmd = new ReceiveMessageCommand({
|
|
213
|
+
MaxNumberOfMessages: 10,
|
|
214
|
+
QueueUrl: queueUrl,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const messages = await client.send(cmd);
|
|
199
218
|
let didProcess = 0;
|
|
200
219
|
if (messages.Messages) {
|
|
201
220
|
for (const message of messages.Messages) {
|
|
@@ -204,10 +223,12 @@ async function readFromQueue(queueUrl: string) {
|
|
|
204
223
|
|
|
205
224
|
if (message.ReceiptHandle) {
|
|
206
225
|
if (STAMHOOFD.environment !== 'development') {
|
|
207
|
-
|
|
226
|
+
const deleteCmd = new DeleteMessageCommand({
|
|
208
227
|
QueueUrl: queueUrl,
|
|
209
228
|
ReceiptHandle: message.ReceiptHandle,
|
|
210
|
-
})
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await client.send(deleteCmd);
|
|
211
232
|
console.log('[AWS Queue] Deleted from queue');
|
|
212
233
|
}
|
|
213
234
|
}
|
|
@@ -61,8 +61,13 @@ async function balanceEmails() {
|
|
|
61
61
|
systemUser,
|
|
62
62
|
templateType: EmailTemplateType.UserBalanceIncreaseNotification,
|
|
63
63
|
filter: {
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
$or: [
|
|
65
|
+
// The amount has increased since the last reminder
|
|
66
|
+
{ reminderAmountIncreased: true },
|
|
67
|
+
|
|
68
|
+
// Or we didn't send a reminder at all yet (since the last time it was zero)
|
|
69
|
+
{ reminderEmailCount: 0 },
|
|
70
|
+
],
|
|
66
71
|
},
|
|
67
72
|
});
|
|
68
73
|
|
|
@@ -74,8 +79,13 @@ async function balanceEmails() {
|
|
|
74
79
|
systemUser,
|
|
75
80
|
templateType: EmailTemplateType.OrganizationBalanceIncreaseNotification,
|
|
76
81
|
filter: {
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
$or: [
|
|
83
|
+
// The amount has increased since the last reminder
|
|
84
|
+
{ reminderAmountIncreased: true },
|
|
85
|
+
|
|
86
|
+
// Or we didn't send a reminder at all yet (since the last time it was zero)
|
|
87
|
+
{ reminderEmailCount: 0 },
|
|
88
|
+
],
|
|
79
89
|
},
|
|
80
90
|
subfilter: organization.privateMeta.balanceNotificationSettings.organizationContactsFilter,
|
|
81
91
|
});
|
|
@@ -147,7 +157,10 @@ async function sendTemplate({
|
|
|
147
157
|
subfilter?: StamhoofdFilter;
|
|
148
158
|
}) {
|
|
149
159
|
// Do not send to persons that already received a similar email before this date
|
|
150
|
-
const weekAgo = new Date(
|
|
160
|
+
const weekAgo = new Date(
|
|
161
|
+
new Date().getTime() - 1000 * 60 * 60 * 24 * Math.max(1, organization.privateMeta.balanceNotificationSettings.minimumDaysBetween)
|
|
162
|
+
+ 12 * 1000 * 60 * 60, // Add a half day offset so we don't get trapped in small differences in time of sending
|
|
163
|
+
); // 5 instead of 7 so the email received is on another working day
|
|
151
164
|
|
|
152
165
|
const model = new Email();
|
|
153
166
|
model.userId = null; // This is a system e-mail
|
|
@@ -172,18 +185,6 @@ async function sendTemplate({
|
|
|
172
185
|
],
|
|
173
186
|
},
|
|
174
187
|
filter,
|
|
175
|
-
/* {
|
|
176
|
-
// Do not send if already received any email very recently
|
|
177
|
-
$not: {
|
|
178
|
-
emails: {
|
|
179
|
-
$elemMatch: {
|
|
180
|
-
sentAt: {
|
|
181
|
-
$gt: weekAgo,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
}, */
|
|
187
188
|
],
|
|
188
189
|
|
|
189
190
|
},
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Email, Member } from '@stamhoofd/models';
|
|
2
|
+
import { SQL } from '@stamhoofd/sql';
|
|
3
|
+
import { EmailRecipient, EmailRecipientFilterType, LimitedFilteredRequest, PaginatedResponse, RegistrationsBlob, mergeFilters } from '@stamhoofd/structures';
|
|
4
|
+
import { GetRegistrationsEndpoint } from '../endpoints/global/registration/GetRegistrationsEndpoint';
|
|
5
|
+
import { memberJoin } from '../sql-filters/registrations';
|
|
6
|
+
|
|
7
|
+
async function getRecipients(result: PaginatedResponse<RegistrationsBlob, LimitedFilteredRequest>, type: 'member' | 'parents' | 'unverified') {
|
|
8
|
+
const recipients: EmailRecipient[] = [];
|
|
9
|
+
|
|
10
|
+
for (const registration of result.results.registrations) {
|
|
11
|
+
const memberRecipients = registration.member.getEmailRecipients([type]);
|
|
12
|
+
recipients.push(...memberRecipients);
|
|
13
|
+
}
|
|
14
|
+
return recipients;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Email.recipientLoaders.set(EmailRecipientFilterType.RegistrationMembers, {
|
|
18
|
+
fetch: async (query: LimitedFilteredRequest) => {
|
|
19
|
+
const result = await GetRegistrationsEndpoint.buildData(query);
|
|
20
|
+
|
|
21
|
+
return new PaginatedResponse({
|
|
22
|
+
results: await getRecipients(result, 'member'),
|
|
23
|
+
next: result.next,
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
count: async (query: LimitedFilteredRequest) => {
|
|
28
|
+
query.filter = mergeFilters([query.filter, {
|
|
29
|
+
member: {
|
|
30
|
+
$elemMatch: {
|
|
31
|
+
$and: [
|
|
32
|
+
{
|
|
33
|
+
email: {
|
|
34
|
+
$neq: null,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
email: {
|
|
39
|
+
$neq: '',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}]);
|
|
46
|
+
const q = await GetRegistrationsEndpoint.buildQuery(query);
|
|
47
|
+
return await q.count();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
Email.recipientLoaders.set(EmailRecipientFilterType.RegistrationParents, {
|
|
52
|
+
fetch: async (query: LimitedFilteredRequest) => {
|
|
53
|
+
const result = await GetRegistrationsEndpoint.buildData(query);
|
|
54
|
+
|
|
55
|
+
return new PaginatedResponse({
|
|
56
|
+
results: await getRecipients(result, 'parents'),
|
|
57
|
+
next: result.next,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
count: async (query: LimitedFilteredRequest) => {
|
|
62
|
+
const q = (await GetRegistrationsEndpoint.buildQuery(query)).join(memberJoin);
|
|
63
|
+
|
|
64
|
+
return await q.sum(
|
|
65
|
+
SQL.jsonLength(SQL.column(Member.table, 'details'), '$.value.parents[*].email'),
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
Email.recipientLoaders.set(EmailRecipientFilterType.RegistrationUnverified, {
|
|
71
|
+
fetch: async (query: LimitedFilteredRequest) => {
|
|
72
|
+
const result = await GetRegistrationsEndpoint.buildData(query);
|
|
73
|
+
|
|
74
|
+
return new PaginatedResponse({
|
|
75
|
+
results: await getRecipients(result, 'unverified'),
|
|
76
|
+
next: result.next,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
count: async (query: LimitedFilteredRequest) => {
|
|
81
|
+
const q = (await GetRegistrationsEndpoint.buildQuery(query)).join(memberJoin);
|
|
82
|
+
|
|
83
|
+
return await q.sum(
|
|
84
|
+
SQL.jsonLength(SQL.column('details'), '$.value.unverifiedEmails'),
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
|
-
import { City } from '@stamhoofd/models';
|
|
4
|
-
import { Province } from '@stamhoofd/models';
|
|
3
|
+
import { City, Province } from '@stamhoofd/models';
|
|
5
4
|
import { City as CityStruct, Country, Province as ProvinceStruct, SearchRegions } from '@stamhoofd/structures';
|
|
6
5
|
import { StringCompare } from '@stamhoofd/utility';
|
|
7
6
|
|
|
@@ -71,6 +70,10 @@ export class SearchRegionsEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
71
70
|
const cities = await City.where({ name: match }, {
|
|
72
71
|
limit: 5,
|
|
73
72
|
sort: [
|
|
73
|
+
{
|
|
74
|
+
column: { name: rawQuery },
|
|
75
|
+
direction: 'DESC',
|
|
76
|
+
},
|
|
74
77
|
{
|
|
75
78
|
column: { name: match },
|
|
76
79
|
direction: 'DESC',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
-
import { Email, Organization, OrganizationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, User, UserFactory } from
|
|
3
|
-
import { EmailStatus, Email as EmailStruct, PermissionLevel, Permissions, Version } from
|
|
4
|
-
import { TestUtils } from
|
|
5
|
-
import { testServer } from
|
|
6
|
-
import { PatchEmailEndpoint } from
|
|
2
|
+
import { Email, Organization, OrganizationFactory, RegistrationPeriod, RegistrationPeriodFactory, Token, User, UserFactory } from '@stamhoofd/models';
|
|
3
|
+
import { EmailStatus, Email as EmailStruct, PermissionLevel, Permissions, Version } from '@stamhoofd/structures';
|
|
4
|
+
import { TestUtils } from '@stamhoofd/test-utils';
|
|
5
|
+
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
6
|
+
import { PatchEmailEndpoint } from './PatchEmailEndpoint';
|
|
7
7
|
|
|
8
8
|
const baseUrl = `/v${Version}/email`;
|
|
9
9
|
|
|
@@ -32,7 +32,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
32
32
|
}).create();
|
|
33
33
|
|
|
34
34
|
organization = await new OrganizationFactory({ period })
|
|
35
|
-
|
|
35
|
+
.create();
|
|
36
36
|
|
|
37
37
|
user = await new UserFactory({
|
|
38
38
|
organization,
|
|
@@ -40,7 +40,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
40
40
|
level: PermissionLevel.Read,
|
|
41
41
|
}),
|
|
42
42
|
})
|
|
43
|
-
|
|
43
|
+
.create();
|
|
44
44
|
|
|
45
45
|
token = await Token.createToken(user);
|
|
46
46
|
});
|
|
@@ -50,7 +50,7 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
50
50
|
email.subject = 'test subject';
|
|
51
51
|
email.status = EmailStatus.Draft;
|
|
52
52
|
email.text = 'test email {{unsubscribeUrl}}';
|
|
53
|
-
email.html =
|
|
53
|
+
email.html = `<!DOCTYPE html>
|
|
54
54
|
<html>
|
|
55
55
|
|
|
56
56
|
<head>
|
|
@@ -66,37 +66,37 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
66
66
|
|
|
67
67
|
</html>`;
|
|
68
68
|
email.json = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
text: 'test email',
|
|
74
|
+
type: 'text',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
type: 'paragraph',
|
|
78
|
+
},
|
|
79
79
|
],
|
|
80
|
-
|
|
80
|
+
type: 'doc',
|
|
81
81
|
};
|
|
82
82
|
email.userId = user.id;
|
|
83
83
|
email.organizationId = organization.id;
|
|
84
84
|
|
|
85
85
|
await email.save();
|
|
86
86
|
|
|
87
|
-
const body = EmailStruct.create({...email, fromAddress:'test@test.be', status: EmailStatus.Sending})
|
|
87
|
+
const body = EmailStruct.create({ ...email, fromAddress: 'test@test.be', status: EmailStatus.Sending });
|
|
88
88
|
|
|
89
89
|
await expect(async () => await patchEmail(body, token, organization))
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
})
|
|
90
|
+
.rejects
|
|
91
|
+
.toThrow('Missing unsubscribe button');
|
|
92
|
+
});
|
|
93
93
|
|
|
94
94
|
test('Should throw error if no unsubscribe button in email text', async () => {
|
|
95
95
|
const email = new Email();
|
|
96
96
|
email.subject = 'test subject';
|
|
97
97
|
email.status = EmailStatus.Draft;
|
|
98
98
|
email.text = 'test email';
|
|
99
|
-
email.html =
|
|
99
|
+
email.html = `<!DOCTYPE html>
|
|
100
100
|
<html>
|
|
101
101
|
|
|
102
102
|
<head>
|
|
@@ -112,28 +112,28 @@ describe('Endpoint.PatchEmailEndpoint', () => {
|
|
|
112
112
|
|
|
113
113
|
</html>`;
|
|
114
114
|
email.json = {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
text: 'test email',
|
|
120
|
+
type: 'text',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
type: 'paragraph',
|
|
124
|
+
},
|
|
125
125
|
],
|
|
126
|
-
|
|
126
|
+
type: 'doc',
|
|
127
127
|
};
|
|
128
128
|
email.userId = user.id;
|
|
129
129
|
email.organizationId = organization.id;
|
|
130
130
|
|
|
131
131
|
await email.save();
|
|
132
132
|
|
|
133
|
-
const body = EmailStruct.create({...email, fromAddress:'test@test.be', status: EmailStatus.Sending})
|
|
133
|
+
const body = EmailStruct.create({ ...email, fromAddress: 'test@test.be', status: EmailStatus.Sending });
|
|
134
134
|
|
|
135
135
|
await expect(async () => await patchEmail(body, token, organization))
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
})
|
|
139
|
-
})
|
|
136
|
+
.rejects
|
|
137
|
+
.toThrow('Missing unsubscribe button');
|
|
138
|
+
});
|
|
139
|
+
});
|