@stamhoofd/backend 2.24.0 → 2.25.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/package.json +10 -10
- package/src/crons.ts +9 -14
- package/src/endpoints/auth/CreateAdminEndpoint.ts +43 -31
- package/src/endpoints/auth/SignupEndpoint.ts +1 -1
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +2 -7
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +2 -3
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +3 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +2 -3
- package/src/helpers/ForwardHandler.ts +30 -28
- package/src/helpers/MemberUserSyncer.ts +1 -1
- package/src/helpers/PeriodHelper.ts +99 -3
- package/src/helpers/SetupStepsUpdater.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.25.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"@simonbackx/simple-encoding": "2.15.0",
|
|
37
37
|
"@simonbackx/simple-endpoints": "1.14.0",
|
|
38
38
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
39
|
-
"@stamhoofd/backend-i18n": "2.
|
|
40
|
-
"@stamhoofd/backend-middleware": "2.
|
|
41
|
-
"@stamhoofd/email": "2.
|
|
42
|
-
"@stamhoofd/models": "2.
|
|
43
|
-
"@stamhoofd/queues": "2.
|
|
44
|
-
"@stamhoofd/sql": "2.
|
|
45
|
-
"@stamhoofd/structures": "2.
|
|
46
|
-
"@stamhoofd/utility": "2.
|
|
39
|
+
"@stamhoofd/backend-i18n": "2.25.0",
|
|
40
|
+
"@stamhoofd/backend-middleware": "2.25.0",
|
|
41
|
+
"@stamhoofd/email": "2.25.0",
|
|
42
|
+
"@stamhoofd/models": "2.25.0",
|
|
43
|
+
"@stamhoofd/queues": "2.25.0",
|
|
44
|
+
"@stamhoofd/sql": "2.25.0",
|
|
45
|
+
"@stamhoofd/structures": "2.25.0",
|
|
46
|
+
"@stamhoofd/utility": "2.25.0",
|
|
47
47
|
"archiver": "^7.0.1",
|
|
48
48
|
"aws-sdk": "^2.885.0",
|
|
49
49
|
"axios": "1.6.8",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"postmark": "4.0.2",
|
|
61
61
|
"stripe": "^16.6.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "48963030dd9e90bef4ea1c96db2cf6ddb6e1908a"
|
|
64
64
|
}
|
package/src/crons.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Database } from '@simonbackx/simple-database';
|
|
2
2
|
import { logger, StyledText } from "@simonbackx/simple-logging";
|
|
3
|
-
import { I18n } from '@stamhoofd/backend-i18n';
|
|
4
3
|
import { Email, EmailAddress } from '@stamhoofd/email';
|
|
5
|
-
import { Group, Organization, Payment, Registration, STPackage,
|
|
6
|
-
import { QueueHandler } from '@stamhoofd/queues';
|
|
4
|
+
import { Group, Organization, Payment, Registration, STPackage, Webshop } from '@stamhoofd/models';
|
|
7
5
|
import { PaymentMethod, PaymentProvider, PaymentStatus } from '@stamhoofd/structures';
|
|
8
6
|
import { Formatter, sleep } from '@stamhoofd/utility';
|
|
9
7
|
import AWS from 'aws-sdk';
|
|
@@ -123,9 +121,7 @@ async function checkWebshopDNS() {
|
|
|
123
121
|
console.log("[DNS] Checking webshop DNS...")
|
|
124
122
|
|
|
125
123
|
for (const webshop of webshops) {
|
|
126
|
-
|
|
127
|
-
console.log("[DNS] Webshop "+webshop.meta.name+" ("+webshop.id+")"+" ("+webshop.domain+")")
|
|
128
|
-
}
|
|
124
|
+
console.log("[DNS] Webshop "+webshop.meta.name+" ("+webshop.id+")"+" ("+webshop.domain+")")
|
|
129
125
|
await webshop.updateDNSRecords()
|
|
130
126
|
}
|
|
131
127
|
|
|
@@ -133,10 +129,10 @@ async function checkWebshopDNS() {
|
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
async function checkReplies() {
|
|
136
|
-
if (STAMHOOFD.environment !== "production") {
|
|
137
|
-
return
|
|
132
|
+
if (STAMHOOFD.environment !== "production" || !STAMHOOFD.AWS_ACCESS_KEY_ID) {
|
|
133
|
+
return
|
|
138
134
|
}
|
|
139
|
-
|
|
135
|
+
|
|
140
136
|
console.log("Checking replies from AWS SQS")
|
|
141
137
|
const sqs = new AWS.SQS();
|
|
142
138
|
const messages = await sqs.receiveMessage({ QueueUrl: "https://sqs.eu-west-1.amazonaws.com/118244293157/stamhoofd-email-forwarding", MaxNumberOfMessages: 10 }).promise()
|
|
@@ -261,7 +257,7 @@ async function checkPostmarkBounces() {
|
|
|
261
257
|
}
|
|
262
258
|
|
|
263
259
|
async function checkBounces() {
|
|
264
|
-
if (STAMHOOFD.environment !== "production") {
|
|
260
|
+
if (STAMHOOFD.environment !== "production" || !STAMHOOFD.AWS_ACCESS_KEY_ID) {
|
|
265
261
|
return
|
|
266
262
|
}
|
|
267
263
|
|
|
@@ -343,7 +339,7 @@ async function checkBounces() {
|
|
|
343
339
|
}
|
|
344
340
|
|
|
345
341
|
async function checkComplaints() {
|
|
346
|
-
if (STAMHOOFD.environment !== "production") {
|
|
342
|
+
if (STAMHOOFD.environment !== "production" || !STAMHOOFD.AWS_ACCESS_KEY_ID) {
|
|
347
343
|
return
|
|
348
344
|
}
|
|
349
345
|
|
|
@@ -396,11 +392,10 @@ async function checkComplaints() {
|
|
|
396
392
|
console.error("[AWS COMPLAINTS] Received virus / fraud complaint!")
|
|
397
393
|
console.error("[AWS COMPLAINTS]", complaint)
|
|
398
394
|
if (STAMHOOFD.environment === "production") {
|
|
399
|
-
Email.
|
|
400
|
-
to: "simon@stamhoofd.be",
|
|
395
|
+
Email.sendWebmaster({
|
|
401
396
|
subject: "Received a "+type+" email notification",
|
|
402
397
|
text: "We received a "+type+" notification for an e-mail from the organization: "+organization?.name+". Please check and adjust if needed.\n"
|
|
403
|
-
}
|
|
398
|
+
})
|
|
404
399
|
}
|
|
405
400
|
}
|
|
406
401
|
} else {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { User as UserStruct,UserPermissions, UserWithMembers } from "@stamhoofd/structures";
|
|
4
|
+
import { PasswordToken, sendEmailTemplate, User } from '@stamhoofd/models';
|
|
5
|
+
import { EmailTemplateType, Recipient, Replacement, UserPermissions, User as UserStruct, UserWithMembers } from "@stamhoofd/structures";
|
|
7
6
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
7
|
|
|
9
|
-
import { Context } from '../../helpers/Context';
|
|
10
8
|
import { AuthenticatedStructures } from '../../helpers/AuthenticatedStructures';
|
|
9
|
+
import { Context } from '../../helpers/Context';
|
|
11
10
|
type Params = Record<string, never>;
|
|
12
11
|
type Query = undefined;
|
|
13
12
|
type Body = UserStruct
|
|
@@ -95,11 +94,6 @@ export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
95
94
|
|
|
96
95
|
await admin.save();
|
|
97
96
|
|
|
98
|
-
const { from, replyTo } = {
|
|
99
|
-
from: organization ? organization.getStrongEmail(request.i18n) : Email.getInternalEmailFor(request.i18n),
|
|
100
|
-
replyTo: undefined
|
|
101
|
-
}
|
|
102
|
-
|
|
103
97
|
// Create a password token that is valid for 7 days
|
|
104
98
|
const validUntil = new Date();
|
|
105
99
|
validUntil.setTime(validUntil.getTime() + 7 * 24 * 3600 * 1000);
|
|
@@ -110,28 +104,46 @@ export class CreateAdminEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
110
104
|
const name = organization?.name ?? request.i18n.t("shared.platformName")
|
|
111
105
|
const what = organization ? `de vereniging ${name} op ${request.i18n.t("shared.platformName")}` : `${request.i18n.t("shared.platformName")}`
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
const emailTo = admin.getEmailTo();
|
|
108
|
+
const email: string = typeof emailTo === 'string' ? emailTo : emailTo[0]?.email;
|
|
109
|
+
|
|
110
|
+
await sendEmailTemplate(organization, {
|
|
111
|
+
recipients: [
|
|
112
|
+
Recipient.create({
|
|
113
|
+
email,
|
|
114
|
+
replacements: [
|
|
115
|
+
Replacement.create({
|
|
116
|
+
token: 'greeting',
|
|
117
|
+
value: admin.firstName ? `Dag ${admin.firstName},` : 'Hallo!'
|
|
118
|
+
}),
|
|
119
|
+
Replacement.create({
|
|
120
|
+
token: 'resetUrl',
|
|
121
|
+
value: recoveryUrl
|
|
122
|
+
}),
|
|
123
|
+
Replacement.create({
|
|
124
|
+
token: 'platformOrOrganizationName',
|
|
125
|
+
value: what
|
|
126
|
+
}),
|
|
127
|
+
Replacement.create({
|
|
128
|
+
token: 'inviterName',
|
|
129
|
+
value: user.firstName ?? 'Iemand'
|
|
130
|
+
}),
|
|
131
|
+
Replacement.create({
|
|
132
|
+
token: 'validUntil',
|
|
133
|
+
value: dateTime
|
|
134
|
+
}),
|
|
135
|
+
Replacement.create({
|
|
136
|
+
token: 'email',
|
|
137
|
+
value: admin.email
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
})
|
|
141
|
+
],
|
|
142
|
+
template: {
|
|
143
|
+
type: admin.hasAccount() ? EmailTemplateType.AdminInvitation : EmailTemplateType.AdminInvitationNewUser
|
|
144
|
+
},
|
|
145
|
+
type: 'transactional'
|
|
146
|
+
});
|
|
135
147
|
|
|
136
148
|
return new Response(
|
|
137
149
|
await AuthenticatedStructures.userWithMembers(admin)
|
|
@@ -60,7 +60,7 @@ export class SignupEndpoint extends Endpoint<Params, Query, Body, ResponseBody>
|
|
|
60
60
|
// Send an e-mail to say you already have an account + follow password forgot flow
|
|
61
61
|
const recoveryUrl = await PasswordToken.getPasswordRecoveryUrl(user, organization, request.i18n)
|
|
62
62
|
const { from, replyTo } = {
|
|
63
|
-
from: (user.permissions || !organization ? Email.getInternalEmailFor(request.i18n) : organization.
|
|
63
|
+
from: (user.permissions || !organization ? Email.getInternalEmailFor(request.i18n) : organization.getDefaultFrom(request.i18n)),
|
|
64
64
|
replyTo: undefined
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -107,19 +107,14 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
107
107
|
return url;
|
|
108
108
|
}).catch(async (error) => {
|
|
109
109
|
if (sendEmail) {
|
|
110
|
-
|
|
110
|
+
await sendEmailTemplate(null, {
|
|
111
111
|
template: {
|
|
112
112
|
type: EmailTemplateType.ExcelExportFailed
|
|
113
113
|
},
|
|
114
114
|
recipients: [
|
|
115
115
|
user.createRecipient()
|
|
116
|
-
]
|
|
117
|
-
from: Email.getInternalEmailFor(Context.i18n)
|
|
116
|
+
]
|
|
118
117
|
})
|
|
119
|
-
|
|
120
|
-
if (builder) {
|
|
121
|
-
Email.schedule(builder)
|
|
122
|
-
}
|
|
123
118
|
}
|
|
124
119
|
throw error
|
|
125
120
|
}),
|
|
@@ -83,11 +83,10 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
83
83
|
try {
|
|
84
84
|
limiter.track(organization.id, 1);
|
|
85
85
|
} catch (e) {
|
|
86
|
-
Email.
|
|
87
|
-
to: "hallo@stamhoofd.be",
|
|
86
|
+
Email.sendWebmaster({
|
|
88
87
|
subject: "[Limiet] Limiet bereikt voor aantal inschrijvingen",
|
|
89
88
|
text: "Beste, \nDe limiet werd bereikt voor het aantal inschrijvingen per dag. \nVereniging: "+organization.id+" ("+organization.name+")" + "\n\n" + e.message + "\n\nStamhoofd"
|
|
90
|
-
}
|
|
89
|
+
})
|
|
91
90
|
|
|
92
91
|
throw new SimpleError({
|
|
93
92
|
code: "too_many_emails_period",
|
|
@@ -95,11 +95,10 @@ export class EmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
95
95
|
try {
|
|
96
96
|
limiter.track(organization.id, request.body.recipients.length);
|
|
97
97
|
} catch (e) {
|
|
98
|
-
Email.
|
|
99
|
-
to: "hallo@stamhoofd.be",
|
|
98
|
+
Email.sendWebmaster({
|
|
100
99
|
subject: "[Limiet] Limiet bereikt voor aantal e-mails",
|
|
101
100
|
text: "Beste, \nDe limiet werd bereikt voor het aantal e-mails per dag. \nVereniging: "+organization.id+" ("+organization.name+")" + "\n\n" + e.message + "\n\nStamhoofd"
|
|
102
|
-
}
|
|
101
|
+
})
|
|
103
102
|
|
|
104
103
|
throw new SimpleError({
|
|
105
104
|
code: "too_many_emails_period",
|
|
@@ -178,7 +177,7 @@ export class EmailEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
178
177
|
}
|
|
179
178
|
})
|
|
180
179
|
|
|
181
|
-
let from = organization.
|
|
180
|
+
let from = organization.getDefaultFrom(request.i18n, false, 'broadcast');
|
|
182
181
|
let replyTo: string | undefined = sender.email;
|
|
183
182
|
|
|
184
183
|
// Can we send from this e-mail or reply-to?
|
|
@@ -86,11 +86,10 @@ export class PlaceOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
86
86
|
try {
|
|
87
87
|
limiter.track(organization.id, 1);
|
|
88
88
|
} catch (e) {
|
|
89
|
-
Email.
|
|
90
|
-
to: "hallo@stamhoofd.be",
|
|
89
|
+
Email.sendWebmaster({
|
|
91
90
|
subject: "[Limiet] Limiet bereikt voor aantal bestellingen",
|
|
92
91
|
text: "Beste, \nDe limiet werd bereikt voor het aantal bestellingen per dag. \nVereniging: "+organization.id+" ("+organization.name+")" + "\n\n" + e.message + "\n\nStamhoofd"
|
|
93
|
-
}
|
|
92
|
+
})
|
|
94
93
|
|
|
95
94
|
throw new SimpleError({
|
|
96
95
|
code: "too_many_emails_period",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EmailAddress, EmailInterfaceRecipient } from "@stamhoofd/email";
|
|
1
|
+
import { Email, EmailAddress, EmailInterfaceRecipient } from "@stamhoofd/email";
|
|
2
2
|
import { Organization } from "@stamhoofd/models";
|
|
3
3
|
import { Formatter } from "@stamhoofd/utility";
|
|
4
4
|
import { simpleParser } from "mailparser";
|
|
@@ -26,32 +26,34 @@ export class ForwardHandler {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Unsubscribe email?
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
for (const domain of Object.values(STAMHOOFD.domains.defaultBroadcastEmail ?? {})) {
|
|
30
|
+
if (email && email?.startsWith("unsubscribe+") && email.endsWith('@' + domain)) {
|
|
31
|
+
// Get id
|
|
32
|
+
const id = email.substring("unsubscribe+".length, email.indexOf('@' + domain))
|
|
33
|
+
const model = await EmailAddress.getByID(id)
|
|
34
|
+
|
|
35
|
+
if (model) {
|
|
36
|
+
console.log('[Unsubscribe] Received an unsubscribe request for ' + model.email + ' from ' + from)
|
|
37
|
+
if (model.unsubscribedAll) {
|
|
38
|
+
// Ignore
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
model.unsubscribedAll = true
|
|
42
|
+
await model.save()
|
|
43
|
+
} else {
|
|
44
|
+
console.error('[Unsubscribe] Received an unsubscribe request for unknown ID ' + id + ' from ' + from)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// Forward
|
|
47
|
+
return {
|
|
48
|
+
from: Email.getWebmasterFromEmail(),
|
|
49
|
+
to: Email.getWebmasterToEmail(),
|
|
50
|
+
subject: "E-mail unsubscribe mislukt",
|
|
51
|
+
text: "Beste,\n\nEr werd een unsubscribe gemeld op "+email+" die niet kon worden verwerkt. Gelieve dit na te kijken.\n\nStamhoofd"
|
|
52
|
+
}
|
|
52
53
|
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
53
56
|
}
|
|
54
|
-
return;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
if (receipt.spamVerdict.status != "PASS" || receipt.virusVerdict.status != "PASS" || !(receipt.spfVerdict.status == "PASS" || receipt.dkimVerdict.status == "PASS")) {
|
|
@@ -60,7 +62,7 @@ export class ForwardHandler {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
// Send a new e-mail
|
|
63
|
-
let defaultEmail: EmailInterfaceRecipient[]|string =
|
|
65
|
+
let defaultEmail: EmailInterfaceRecipient[]|string = Email.getWebmasterToEmail()
|
|
64
66
|
let organizationEmails: EmailInterfaceRecipient[] = []
|
|
65
67
|
const extraDescription = "Dit bericht werd verstuurd naar "+email+", en werd automatisch doorgestuurd naar alle beheerders. Stel in Stamhoofd de e-mailadressen in om ervoor te zorgen dat antwoorden naar een specifiek e-mailadres worden verstuurd."
|
|
66
68
|
|
|
@@ -76,7 +78,7 @@ export class ForwardHandler {
|
|
|
76
78
|
|
|
77
79
|
// Send back to receiver without including the original message to avoid spam
|
|
78
80
|
return {
|
|
79
|
-
from: email ??
|
|
81
|
+
from: email ?? Email.getWebmasterToEmail(),
|
|
80
82
|
to: from,
|
|
81
83
|
subject: "Ongeldig e-mailadres",
|
|
82
84
|
text: "Beste,\n\nDe vereniging die je probeert te bereiken via "+email+" is helaas niet bereikbaar via dit e-mailadres. Dit e-mailadres wordt enkel gebruikt voor het versturen van automatische e-mails in naam van een vereniging. Probeer de vereniging te contacteren via een ander e-mailadres.\n\nBedankt."
|
|
@@ -116,7 +118,7 @@ export class ForwardHandler {
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
const options = {
|
|
119
|
-
from: email ??
|
|
121
|
+
from: email ?? Email.getWebmasterToEmail(),
|
|
120
122
|
to: defaultEmail,
|
|
121
123
|
replyTo: parsed.from?.text,
|
|
122
124
|
subject: parsed.subject ?? "Doorgestuurd bericht",
|
|
@@ -137,4 +139,4 @@ export class ForwardHandler {
|
|
|
137
139
|
|
|
138
140
|
return options
|
|
139
141
|
}
|
|
140
|
-
}
|
|
142
|
+
}
|
|
@@ -164,7 +164,7 @@ export class MemberUserSyncerStatic {
|
|
|
164
164
|
let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, {allowWithoutAccount: true})
|
|
165
165
|
|
|
166
166
|
if (user) {
|
|
167
|
-
console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
|
|
167
|
+
//console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
|
|
168
168
|
if (!asParent) {
|
|
169
169
|
if (user.memberId && user.memberId !== member.id) {
|
|
170
170
|
console.error('Found conflicting user with multiple members', user.id, 'members', user.memberId, 'to', member.id)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
import { Organization, OrganizationRegistrationPeriod, RegistrationPeriod } from "@stamhoofd/models";
|
|
2
|
+
import { Member, MemberResponsibilityRecord, Organization, OrganizationRegistrationPeriod, Platform, RegistrationPeriod } from "@stamhoofd/models";
|
|
3
3
|
import { AuthenticatedStructures } from "./AuthenticatedStructures";
|
|
4
4
|
import { PatchOrganizationRegistrationPeriodsEndpoint } from "../endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint";
|
|
5
5
|
import { QueueHandler } from "@stamhoofd/queues";
|
|
6
6
|
import { SetupStepUpdater } from "./SetupStepsUpdater";
|
|
7
|
+
import { PermissionLevel } from "@stamhoofd/structures";
|
|
8
|
+
import { MemberUserSyncer } from "./MemberUserSyncer";
|
|
9
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
7
10
|
|
|
8
11
|
export class PeriodHelper {
|
|
9
12
|
static async moveOrganizationToPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
@@ -14,6 +17,89 @@ export class PeriodHelper {
|
|
|
14
17
|
await organization.save()
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
static async stopAllResponsibilities() {
|
|
21
|
+
console.log('Stopping all responsibilities')
|
|
22
|
+
const platform = await Platform.getSharedPrivateStruct()
|
|
23
|
+
const keepPlatformResponsibilityIds = platform.config.responsibilities.filter(r => !r.organizationBased).map(r => r.id)
|
|
24
|
+
const keepResponsibilityIds = platform.config.responsibilities.filter(r => !r.organizationBased || r.permissions?.level === PermissionLevel.Full).map(r => r.id)
|
|
25
|
+
const batchSize = 100;
|
|
26
|
+
|
|
27
|
+
let lastId = "";
|
|
28
|
+
let c = 0;
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const records = await MemberResponsibilityRecord.where(
|
|
32
|
+
{
|
|
33
|
+
id: { sign: ">", value: lastId },
|
|
34
|
+
endDate: null
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
limit: batchSize,
|
|
38
|
+
sort: ["id"]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
for (const record of records) {
|
|
43
|
+
lastId = record.id;
|
|
44
|
+
|
|
45
|
+
const invalid = keepPlatformResponsibilityIds.includes(record.responsibilityId) && record.organizationId
|
|
46
|
+
|
|
47
|
+
if (!keepResponsibilityIds.includes(record.responsibilityId) || invalid) {
|
|
48
|
+
record.endDate = new Date()
|
|
49
|
+
await record.save()
|
|
50
|
+
c++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (records.length < batchSize) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('Done: stopped all responsibilities: ' + c)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static async syncAllMemberUsers() {
|
|
64
|
+
console.log('Syncing all members')
|
|
65
|
+
|
|
66
|
+
let c = 0;
|
|
67
|
+
let lastId: string = '';
|
|
68
|
+
|
|
69
|
+
while(true) {
|
|
70
|
+
const rawMembers = await Member.where({
|
|
71
|
+
id: {
|
|
72
|
+
value: lastId,
|
|
73
|
+
sign: '>'
|
|
74
|
+
}
|
|
75
|
+
}, {limit: 500, sort: ['id']});
|
|
76
|
+
|
|
77
|
+
if (rawMembers.length === 0) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const membersWithRegistrations = await Member.getBlobByIds(...rawMembers.map(m => m.id));
|
|
82
|
+
|
|
83
|
+
const promises: Promise<any>[] = [];
|
|
84
|
+
|
|
85
|
+
for (const memberWithRegistrations of membersWithRegistrations) {
|
|
86
|
+
promises.push((async () => {
|
|
87
|
+
await MemberUserSyncer.onChangeMember(memberWithRegistrations);
|
|
88
|
+
c++;
|
|
89
|
+
|
|
90
|
+
if (c%10000 === 0) {
|
|
91
|
+
console.log('Synced ' + c + ' members');
|
|
92
|
+
}
|
|
93
|
+
})());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await Promise.all(promises);
|
|
97
|
+
lastId = rawMembers[rawMembers.length - 1].id;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('Done: synced all members: ' + c)
|
|
101
|
+
}
|
|
102
|
+
|
|
17
103
|
static async createOrganizationPeriodForPeriod(organization: Organization, period: RegistrationPeriod) {
|
|
18
104
|
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: period.id, organizationId: organization.id }, {limit: 1})
|
|
19
105
|
|
|
@@ -35,9 +121,14 @@ export class PeriodHelper {
|
|
|
35
121
|
|
|
36
122
|
static async moveAllOrganizationsToPeriod(period: RegistrationPeriod) {
|
|
37
123
|
const tag = "moveAllOrganizationsToPeriod";
|
|
38
|
-
|
|
39
|
-
|
|
124
|
+
if (QueueHandler.isRunning(tag)) {
|
|
125
|
+
throw new SimpleError({
|
|
126
|
+
code: 'move_period_pending',
|
|
127
|
+
message: 'Er is al een jaarovergang bezig. Wacht tot deze klaar is.'
|
|
128
|
+
})
|
|
129
|
+
}
|
|
40
130
|
|
|
131
|
+
const batchSize = 10;
|
|
41
132
|
await QueueHandler.schedule(tag, async () => {
|
|
42
133
|
let lastId = "";
|
|
43
134
|
|
|
@@ -62,9 +153,14 @@ export class PeriodHelper {
|
|
|
62
153
|
}
|
|
63
154
|
|
|
64
155
|
}
|
|
156
|
+
|
|
157
|
+
await this.stopAllResponsibilities()
|
|
158
|
+
await this.syncAllMemberUsers()
|
|
65
159
|
});
|
|
66
160
|
|
|
67
161
|
// When done: update setup steps
|
|
68
162
|
await SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod()
|
|
69
163
|
}
|
|
164
|
+
|
|
165
|
+
|
|
70
166
|
}
|
|
@@ -151,9 +151,7 @@ export class SetupStepUpdater {
|
|
|
151
151
|
) {
|
|
152
152
|
const setupSteps = organizationRegistrationPeriod.setupSteps;
|
|
153
153
|
|
|
154
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
155
154
|
for (const stepType of Object.values(SetupStepType)) {
|
|
156
|
-
console.log(`[STEP TYPE] ${stepType}`);
|
|
157
155
|
const operation = this.STEP_TYPE_OPERATIONS[stepType];
|
|
158
156
|
await operation(setupSteps, organization, platform);
|
|
159
157
|
}
|