@stamhoofd/models 2.63.0 → 2.65.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/dist/src/helpers/EmailBuilder.d.ts +6 -1
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
- package/dist/src/helpers/EmailBuilder.js +60 -20
- package/dist/src/helpers/EmailBuilder.js.map +1 -1
- package/dist/src/helpers/MemberMerger.d.ts.map +1 -1
- package/dist/src/helpers/MemberMerger.js +1 -2
- package/dist/src/helpers/MemberMerger.js.map +1 -1
- package/dist/src/migrations/1605262045-import-postcodes.d.ts +3 -3
- package/dist/src/migrations/1605262045-import-postcodes.js +10 -13
- package/dist/src/migrations/1734429094-registration-trial-until.sql +3 -0
- package/dist/src/migrations/1734429095-membership-trial-until.sql +2 -0
- package/dist/src/migrations/1734535120-registration-period-previous-period-id.sql +3 -0
- package/dist/src/migrations/1734535121-platform-previous-period-id.sql +3 -0
- package/dist/src/migrations/1734626607-cached-balance-amount-open.sql +2 -0
- package/dist/src/migrations/1734698906-cached-balance-amount-paid.sql +2 -0
- package/dist/src/migrations/1735573520-emails-email-type.sql +2 -0
- package/dist/src/migrations/1735573521-email-recipients-email-type.sql +4 -0
- package/dist/src/migrations/1735573522-emails-indexes.sql +3 -0
- package/dist/src/migrations/1735982691-cached-balance-email-reminder-counts.sql +4 -0
- package/dist/src/migrations/1735994471-default-email-templates.sql +5 -0
- package/dist/src/models/AuditLog.d.ts +2 -7
- package/dist/src/models/AuditLog.d.ts.map +1 -1
- package/dist/src/models/AuditLog.js +1 -15
- package/dist/src/models/AuditLog.js.map +1 -1
- package/dist/src/models/BalanceItem.d.ts +4 -13
- package/dist/src/models/BalanceItem.d.ts.map +1 -1
- package/dist/src/models/BalanceItem.js +21 -33
- package/dist/src/models/BalanceItem.js.map +1 -1
- package/dist/src/models/BalanceItemPayment.d.ts +3 -2
- package/dist/src/models/BalanceItemPayment.d.ts.map +1 -1
- package/dist/src/models/BalanceItemPayment.js +2 -1
- package/dist/src/models/BalanceItemPayment.js.map +1 -1
- package/dist/src/models/BuckarooPayment.d.ts +2 -2
- package/dist/src/models/BuckarooPayment.d.ts.map +1 -1
- package/dist/src/models/BuckarooPayment.js +2 -1
- package/dist/src/models/BuckarooPayment.js.map +1 -1
- package/dist/src/models/CachedBalance.d.ts +12 -10
- package/dist/src/models/CachedBalance.d.ts.map +1 -1
- package/dist/src/models/CachedBalance.js +122 -39
- package/dist/src/models/CachedBalance.js.map +1 -1
- package/dist/src/models/Document.d.ts +3 -3
- package/dist/src/models/Document.d.ts.map +1 -1
- package/dist/src/models/Document.js +2 -1
- package/dist/src/models/Document.js.map +1 -1
- package/dist/src/models/DocumentTemplate.d.ts +2 -2
- package/dist/src/models/DocumentTemplate.d.ts.map +1 -1
- package/dist/src/models/DocumentTemplate.js +38 -10
- package/dist/src/models/DocumentTemplate.js.map +1 -1
- package/dist/src/models/Email.d.ts +10 -4
- package/dist/src/models/Email.d.ts.map +1 -1
- package/dist/src/models/Email.js +68 -25
- package/dist/src/models/Email.js.map +1 -1
- package/dist/src/models/EmailRecipient.d.ts +14 -8
- package/dist/src/models/EmailRecipient.d.ts.map +1 -1
- package/dist/src/models/EmailRecipient.js +19 -14
- package/dist/src/models/EmailRecipient.js.map +1 -1
- package/dist/src/models/EmailTemplate.d.ts +2 -7
- package/dist/src/models/EmailTemplate.d.ts.map +1 -1
- package/dist/src/models/EmailTemplate.js +1 -15
- package/dist/src/models/EmailTemplate.js.map +1 -1
- package/dist/src/models/EmailVerificationCode.d.ts +2 -2
- package/dist/src/models/EmailVerificationCode.d.ts.map +1 -1
- package/dist/src/models/EmailVerificationCode.js +2 -1
- package/dist/src/models/EmailVerificationCode.js.map +1 -1
- package/dist/src/models/Event.d.ts +2 -2
- package/dist/src/models/Event.d.ts.map +1 -1
- package/dist/src/models/Event.js +2 -1
- package/dist/src/models/Event.js.map +1 -1
- package/dist/src/models/Group.d.ts +2 -7
- package/dist/src/models/Group.d.ts.map +1 -1
- package/dist/src/models/Group.js +1 -17
- package/dist/src/models/Group.js.map +1 -1
- package/dist/src/models/Image.d.ts +2 -2
- package/dist/src/models/Image.d.ts.map +1 -1
- package/dist/src/models/Image.js +2 -1
- package/dist/src/models/Image.js.map +1 -1
- package/dist/src/models/Member.d.ts +6 -9
- package/dist/src/models/Member.d.ts.map +1 -1
- package/dist/src/models/Member.js +3 -42
- package/dist/src/models/Member.js.map +1 -1
- package/dist/src/models/MemberPlatformMembership.d.ts +12 -8
- package/dist/src/models/MemberPlatformMembership.d.ts.map +1 -1
- package/dist/src/models/MemberPlatformMembership.js +80 -20
- package/dist/src/models/MemberPlatformMembership.js.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.d.ts +2 -7
- package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -1
- package/dist/src/models/MemberResponsibilityRecord.js +1 -15
- package/dist/src/models/MemberResponsibilityRecord.js.map +1 -1
- package/dist/src/models/MemberUser.d.ts +8 -0
- package/dist/src/models/MemberUser.d.ts.map +1 -0
- package/dist/src/models/MemberUser.js +26 -0
- package/dist/src/models/MemberUser.js.map +1 -0
- package/dist/src/models/MergedMember.d.ts +3 -3
- package/dist/src/models/MergedMember.d.ts.map +1 -1
- package/dist/src/models/MergedMember.js +3 -2
- package/dist/src/models/MergedMember.js.map +1 -1
- package/dist/src/models/MolliePayment.d.ts +2 -2
- package/dist/src/models/MolliePayment.d.ts.map +1 -1
- package/dist/src/models/MolliePayment.js +2 -1
- package/dist/src/models/MolliePayment.js.map +1 -1
- package/dist/src/models/MollieToken.d.ts +2 -2
- package/dist/src/models/MollieToken.d.ts.map +1 -1
- package/dist/src/models/MollieToken.js +2 -1
- package/dist/src/models/MollieToken.js.map +1 -1
- package/dist/src/models/OneTimeToken.d.ts +2 -2
- package/dist/src/models/OneTimeToken.d.ts.map +1 -1
- package/dist/src/models/OneTimeToken.js +2 -1
- package/dist/src/models/OneTimeToken.js.map +1 -1
- package/dist/src/models/Order.d.ts +3 -2
- package/dist/src/models/Order.d.ts.map +1 -1
- package/dist/src/models/Order.js +2 -1
- package/dist/src/models/Order.js.map +1 -1
- package/dist/src/models/Organization.d.ts +2 -2
- package/dist/src/models/Organization.d.ts.map +1 -1
- package/dist/src/models/Organization.js +2 -1
- package/dist/src/models/Organization.js.map +1 -1
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts +2 -2
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts.map +1 -1
- package/dist/src/models/OrganizationRegistrationPeriod.js +2 -1
- package/dist/src/models/OrganizationRegistrationPeriod.js.map +1 -1
- package/dist/src/models/PasswordToken.d.ts +3 -2
- package/dist/src/models/PasswordToken.d.ts.map +1 -1
- package/dist/src/models/PasswordToken.js +2 -1
- package/dist/src/models/PasswordToken.js.map +1 -1
- package/dist/src/models/PayconiqPayment.d.ts +2 -2
- package/dist/src/models/PayconiqPayment.d.ts.map +1 -1
- package/dist/src/models/PayconiqPayment.js +2 -1
- package/dist/src/models/PayconiqPayment.js.map +1 -1
- package/dist/src/models/Payment.d.ts +2 -7
- package/dist/src/models/Payment.d.ts.map +1 -1
- package/dist/src/models/Payment.js +1 -15
- package/dist/src/models/Payment.js.map +1 -1
- package/dist/src/models/Platform.d.ts +5 -3
- package/dist/src/models/Platform.d.ts.map +1 -1
- package/dist/src/models/Platform.js +11 -2
- package/dist/src/models/Platform.js.map +1 -1
- package/dist/src/models/RegisterCode.d.ts +2 -2
- package/dist/src/models/RegisterCode.d.ts.map +1 -1
- package/dist/src/models/RegisterCode.js +2 -1
- package/dist/src/models/RegisterCode.js.map +1 -1
- package/dist/src/models/Registration.d.ts +20 -7
- package/dist/src/models/Registration.d.ts.map +1 -1
- package/dist/src/models/Registration.js +25 -61
- package/dist/src/models/Registration.js.map +1 -1
- package/dist/src/models/RegistrationPeriod.d.ts +4 -2
- package/dist/src/models/RegistrationPeriod.d.ts.map +1 -1
- package/dist/src/models/RegistrationPeriod.js +23 -1
- package/dist/src/models/RegistrationPeriod.js.map +1 -1
- package/dist/src/models/STCredit.d.ts +2 -2
- package/dist/src/models/STCredit.d.ts.map +1 -1
- package/dist/src/models/STCredit.js +2 -1
- package/dist/src/models/STCredit.js.map +1 -1
- package/dist/src/models/STInvoice.d.ts +3 -2
- package/dist/src/models/STInvoice.d.ts.map +1 -1
- package/dist/src/models/STInvoice.js +2 -1
- package/dist/src/models/STInvoice.js.map +1 -1
- package/dist/src/models/STPackage.d.ts +2 -2
- package/dist/src/models/STPackage.d.ts.map +1 -1
- package/dist/src/models/STPackage.js +2 -1
- package/dist/src/models/STPackage.js.map +1 -1
- package/dist/src/models/STPendingInvoice.d.ts +3 -2
- package/dist/src/models/STPendingInvoice.d.ts.map +1 -1
- package/dist/src/models/STPendingInvoice.js +2 -1
- package/dist/src/models/STPendingInvoice.js.map +1 -1
- package/dist/src/models/StripeAccount.d.ts +2 -2
- package/dist/src/models/StripeAccount.d.ts.map +1 -1
- package/dist/src/models/StripeAccount.js +2 -1
- package/dist/src/models/StripeAccount.js.map +1 -1
- package/dist/src/models/StripeCheckoutSession.d.ts +2 -2
- package/dist/src/models/StripeCheckoutSession.d.ts.map +1 -1
- package/dist/src/models/StripeCheckoutSession.js +2 -1
- package/dist/src/models/StripeCheckoutSession.js.map +1 -1
- package/dist/src/models/StripePaymentIntent.d.ts +2 -2
- package/dist/src/models/StripePaymentIntent.d.ts.map +1 -1
- package/dist/src/models/StripePaymentIntent.js +2 -1
- package/dist/src/models/StripePaymentIntent.js.map +1 -1
- package/dist/src/models/Ticket.d.ts +3 -2
- package/dist/src/models/Ticket.d.ts.map +1 -1
- package/dist/src/models/Ticket.js +2 -1
- package/dist/src/models/Ticket.js.map +1 -1
- package/dist/src/models/Token.d.ts +3 -2
- package/dist/src/models/Token.d.ts.map +1 -1
- package/dist/src/models/Token.js +2 -1
- package/dist/src/models/Token.js.map +1 -1
- package/dist/src/models/UsedRegisterCode.d.ts +2 -2
- package/dist/src/models/UsedRegisterCode.d.ts.map +1 -1
- package/dist/src/models/UsedRegisterCode.js +2 -1
- package/dist/src/models/UsedRegisterCode.js.map +1 -1
- package/dist/src/models/User.d.ts +7 -2
- package/dist/src/models/User.d.ts.map +1 -1
- package/dist/src/models/User.js +27 -4
- package/dist/src/models/User.js.map +1 -1
- package/dist/src/models/UserPermissions.d.ts +3 -2
- package/dist/src/models/UserPermissions.d.ts.map +1 -1
- package/dist/src/models/UserPermissions.js +2 -1
- package/dist/src/models/UserPermissions.js.map +1 -1
- package/dist/src/models/Webshop.d.ts +3 -2
- package/dist/src/models/Webshop.d.ts.map +1 -1
- package/dist/src/models/Webshop.js +2 -1
- package/dist/src/models/Webshop.js.map +1 -1
- package/dist/src/models/WebshopDiscountCode.d.ts +2 -2
- package/dist/src/models/WebshopDiscountCode.d.ts.map +1 -1
- package/dist/src/models/WebshopDiscountCode.js +2 -1
- package/dist/src/models/WebshopDiscountCode.js.map +1 -1
- package/dist/src/models/addresses/City.d.ts +3 -2
- package/dist/src/models/addresses/City.d.ts.map +1 -1
- package/dist/src/models/addresses/City.js +2 -1
- package/dist/src/models/addresses/City.js.map +1 -1
- package/dist/src/models/addresses/PostalCode.d.ts +3 -2
- package/dist/src/models/addresses/PostalCode.d.ts.map +1 -1
- package/dist/src/models/addresses/PostalCode.js +2 -1
- package/dist/src/models/addresses/PostalCode.js.map +1 -1
- package/dist/src/models/addresses/Province.d.ts +2 -2
- package/dist/src/models/addresses/Province.d.ts.map +1 -1
- package/dist/src/models/addresses/Province.js +2 -1
- package/dist/src/models/addresses/Province.js.map +1 -1
- package/dist/src/models/addresses/Street.d.ts +3 -2
- package/dist/src/models/addresses/Street.d.ts.map +1 -1
- package/dist/src/models/addresses/Street.js +2 -1
- package/dist/src/models/addresses/Street.js.map +1 -1
- package/dist/src/models/index.d.ts +1 -0
- package/dist/src/models/index.d.ts.map +1 -1
- package/dist/src/models/index.js +1 -0
- package/dist/src/models/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/helpers/EmailBuilder.ts +82 -27
- package/src/helpers/MemberMerger.ts +2 -3
- package/src/migrations/1605262045-import-postcodes.ts +6 -9
- package/src/migrations/1734429094-registration-trial-until.sql +3 -0
- package/src/migrations/1734429095-membership-trial-until.sql +2 -0
- package/src/migrations/1734535120-registration-period-previous-period-id.sql +3 -0
- package/src/migrations/1734535121-platform-previous-period-id.sql +3 -0
- package/src/migrations/1734626607-cached-balance-amount-open.sql +2 -0
- package/src/migrations/1734698906-cached-balance-amount-paid.sql +2 -0
- package/src/migrations/1735573520-emails-email-type.sql +2 -0
- package/src/migrations/1735573521-email-recipients-email-type.sql +4 -0
- package/src/migrations/1735573522-emails-indexes.sql +3 -0
- package/src/migrations/1735982691-cached-balance-email-reminder-counts.sql +4 -0
- package/src/migrations/1735994471-default-email-templates.sql +5 -0
- package/src/models/AuditLog.ts +3 -21
- package/src/models/BalanceItem.ts +30 -46
- package/src/models/BalanceItemPayment.ts +3 -2
- package/src/models/BuckarooPayment.ts +3 -2
- package/src/models/CachedBalance.ts +166 -46
- package/src/models/Document.ts +4 -3
- package/src/models/DocumentTemplate.ts +43 -12
- package/src/models/Email.ts +80 -32
- package/src/models/EmailRecipient.ts +20 -20
- package/src/models/EmailTemplate.ts +3 -21
- package/src/models/EmailVerificationCode.ts +3 -2
- package/src/models/Event.ts +3 -2
- package/src/models/Group.ts +4 -23
- package/src/models/Image.ts +3 -2
- package/src/models/Member.ts +6 -52
- package/src/models/MemberPlatformMembership.ts +95 -26
- package/src/models/MemberResponsibilityRecord.ts +3 -21
- package/src/models/MemberUser.ts +18 -0
- package/src/models/MergedMember.ts +4 -3
- package/src/models/MolliePayment.ts +3 -2
- package/src/models/MollieToken.ts +3 -2
- package/src/models/OneTimeToken.ts +3 -2
- package/src/models/Order.ts +3 -2
- package/src/models/Organization.ts +3 -2
- package/src/models/OrganizationRegistrationPeriod.ts +3 -2
- package/src/models/PasswordToken.ts +3 -2
- package/src/models/PayconiqPayment.ts +3 -2
- package/src/models/Payment.ts +3 -21
- package/src/models/Platform.ts +13 -4
- package/src/models/RegisterCode.ts +3 -2
- package/src/models/Registration.ts +24 -68
- package/src/models/RegistrationPeriod.ts +30 -3
- package/src/models/STCredit.ts +3 -2
- package/src/models/STInvoice.ts +3 -2
- package/src/models/STPackage.ts +3 -2
- package/src/models/STPendingInvoice.ts +3 -2
- package/src/models/StripeAccount.ts +3 -2
- package/src/models/StripeCheckoutSession.ts +3 -2
- package/src/models/StripePaymentIntent.ts +3 -2
- package/src/models/Ticket.ts +3 -2
- package/src/models/Token.ts +3 -2
- package/src/models/UsedRegisterCode.ts +3 -2
- package/src/models/User.ts +31 -3
- package/src/models/UserPermissions.ts +3 -2
- package/src/models/Webshop.ts +3 -2
- package/src/models/WebshopDiscountCode.ts +3 -2
- package/src/models/addresses/City.ts +3 -2
- package/src/models/addresses/PostalCode.ts +3 -2
- package/src/models/addresses/Province.ts +3 -2
- package/src/models/addresses/Street.ts +3 -2
- package/src/models/index.ts +1 -0
package/src/models/Email.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { column
|
|
2
|
-
import {
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import { EmailAttachment, EmailPreview, EmailRecipientFilter, EmailRecipientFilterType, EmailRecipientsStatus, EmailRecipient as EmailRecipientStruct, EmailStatus, Email as EmailStruct, EmailTemplateType, getExampleRecipient, LimitedFilteredRequest, PaginatedResponse, Recipient, Replacement, SortItemDirection, StamhoofdFilter } from '@stamhoofd/structures';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
import { AnyDecoder, ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
@@ -7,13 +7,14 @@ import { SimpleError } from '@simonbackx/simple-errors';
|
|
|
7
7
|
import { I18n } from '@stamhoofd/backend-i18n';
|
|
8
8
|
import { Email as EmailClass } from '@stamhoofd/email';
|
|
9
9
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
10
|
-
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
10
|
+
import { QueryableModel, SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
11
11
|
import { Formatter } from '@stamhoofd/utility';
|
|
12
|
-
import { getEmailBuilder } from '../helpers/EmailBuilder';
|
|
12
|
+
import { fillRecipientReplacements, getEmailBuilder } from '../helpers/EmailBuilder';
|
|
13
13
|
import { EmailRecipient } from './EmailRecipient';
|
|
14
14
|
import { Organization } from './Organization';
|
|
15
|
+
import { EmailTemplate } from './EmailTemplate';
|
|
15
16
|
|
|
16
|
-
export class Email extends
|
|
17
|
+
export class Email extends QueryableModel {
|
|
17
18
|
static table = 'emails';
|
|
18
19
|
|
|
19
20
|
@column({
|
|
@@ -32,6 +33,13 @@ export class Email extends Model {
|
|
|
32
33
|
@column({ type: 'json', decoder: EmailRecipientFilter })
|
|
33
34
|
recipientFilter: EmailRecipientFilter = EmailRecipientFilter.create({});
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Helper to prevent sending too many emails to the same person.
|
|
38
|
+
* Allows for filtering on objects that didn't receive a specific email yet
|
|
39
|
+
*/
|
|
40
|
+
@column({ type: 'string', nullable: true })
|
|
41
|
+
emailType: string | null = null;
|
|
42
|
+
|
|
35
43
|
@column({ type: 'string', nullable: true })
|
|
36
44
|
subject: string | null;
|
|
37
45
|
|
|
@@ -204,7 +212,29 @@ export class Email extends Model {
|
|
|
204
212
|
return '"' + cleanedName + '" <' + address + '>';
|
|
205
213
|
}
|
|
206
214
|
|
|
207
|
-
async
|
|
215
|
+
async setFromTemplate(type: EmailTemplateType) {
|
|
216
|
+
// Most specific template: for specific group
|
|
217
|
+
let templates = (await EmailTemplate.where({ type, organizationId: this.organizationId, groupId: null, webshopId: null }));
|
|
218
|
+
|
|
219
|
+
// Then default
|
|
220
|
+
if (templates.length === 0 && this.organizationId) {
|
|
221
|
+
templates = (await EmailTemplate.where({ type, organizationId: null, groupId: null, webshopId: null }));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (templates.length === 0) {
|
|
225
|
+
// No default
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const defaultTemplate = templates[0];
|
|
229
|
+
this.html = defaultTemplate.html;
|
|
230
|
+
this.text = defaultTemplate.text;
|
|
231
|
+
this.subject = defaultTemplate.subject;
|
|
232
|
+
this.json = defaultTemplate.json;
|
|
233
|
+
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async send(): Promise<Email | null> {
|
|
208
238
|
this.throwIfNotReadyToSend();
|
|
209
239
|
await this.save();
|
|
210
240
|
|
|
@@ -218,10 +248,27 @@ export class Email extends Model {
|
|
|
218
248
|
human: 'De e-mail die je probeert te versturen bestaat niet meer',
|
|
219
249
|
});
|
|
220
250
|
}
|
|
221
|
-
if (upToDate.status === EmailStatus.Sent) {
|
|
251
|
+
if (upToDate.status === EmailStatus.Sent || upToDate.status === EmailStatus.Failed) {
|
|
222
252
|
// Already done
|
|
223
253
|
// In other cases -> queue has stopped and we can retry
|
|
224
|
-
return;
|
|
254
|
+
return upToDate;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (upToDate.status === EmailStatus.Sending) {
|
|
258
|
+
// This is a retry.
|
|
259
|
+
if (upToDate.emailType) {
|
|
260
|
+
// Not eligible for retry
|
|
261
|
+
upToDate.status = EmailStatus.Failed;
|
|
262
|
+
await upToDate.save();
|
|
263
|
+
return upToDate;
|
|
264
|
+
}
|
|
265
|
+
if (upToDate.createdAt < new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 2)) {
|
|
266
|
+
// Too long
|
|
267
|
+
console.error('Email has been sending for too long. Marking as failed...', upToDate.id);
|
|
268
|
+
upToDate.status = EmailStatus.Failed;
|
|
269
|
+
await upToDate.save();
|
|
270
|
+
return upToDate;
|
|
271
|
+
}
|
|
225
272
|
}
|
|
226
273
|
const organization = upToDate.organizationId ? await Organization.getByID(upToDate.organizationId) : null;
|
|
227
274
|
upToDate.throwIfNotReadyToSend();
|
|
@@ -345,9 +392,7 @@ export class Email extends Model {
|
|
|
345
392
|
});
|
|
346
393
|
sendingPromises.push(promise);
|
|
347
394
|
|
|
348
|
-
const virtualRecipient =
|
|
349
|
-
...recipient,
|
|
350
|
-
});
|
|
395
|
+
const virtualRecipient = recipient.getRecipient();
|
|
351
396
|
|
|
352
397
|
const callback = async (error: Error | null) => {
|
|
353
398
|
if (error === null) {
|
|
@@ -378,7 +423,7 @@ export class Email extends Model {
|
|
|
378
423
|
replyTo,
|
|
379
424
|
subject: upToDate.subject!,
|
|
380
425
|
html: upToDate.html!,
|
|
381
|
-
type: 'broadcast',
|
|
426
|
+
type: upToDate.emailType ? 'transactional' : 'broadcast',
|
|
382
427
|
attachments,
|
|
383
428
|
callback(error: Error | null) {
|
|
384
429
|
callback(error).catch(console.error);
|
|
@@ -391,11 +436,18 @@ export class Email extends Model {
|
|
|
391
436
|
await Promise.all(sendingPromises);
|
|
392
437
|
}
|
|
393
438
|
|
|
394
|
-
|
|
439
|
+
if (upToDate.recipientCount === 0 && upToDate.userId === null) {
|
|
440
|
+
// We only delete automated emails (email type) if they have no recipients
|
|
441
|
+
console.log('No recipients found for email ', upToDate.id, ' deleting...');
|
|
442
|
+
await upToDate.delete();
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
395
445
|
|
|
446
|
+
console.log('Finished sending email', upToDate.id);
|
|
396
447
|
// Mark email as sent
|
|
397
448
|
upToDate.status = EmailStatus.Sent;
|
|
398
449
|
await upToDate.save();
|
|
450
|
+
return upToDate;
|
|
399
451
|
});
|
|
400
452
|
}
|
|
401
453
|
|
|
@@ -498,18 +550,19 @@ export class Email extends Model {
|
|
|
498
550
|
let request: LimitedFilteredRequest | null = new LimitedFilteredRequest({
|
|
499
551
|
filter: subfilter.filter,
|
|
500
552
|
sort: [{ key: 'id', order: SortItemDirection.ASC }],
|
|
501
|
-
limit:
|
|
553
|
+
limit: 100,
|
|
502
554
|
search: subfilter.search,
|
|
503
555
|
});
|
|
504
556
|
|
|
505
557
|
while (request) {
|
|
506
|
-
console.log('Loading email page', subfilter.type, request);
|
|
507
558
|
const response = await loader.fetch(request, subfilter.subfilter);
|
|
508
559
|
|
|
509
560
|
count += response.results.length;
|
|
510
561
|
|
|
511
562
|
for (const item of response.results) {
|
|
512
563
|
const recipient = new EmailRecipient();
|
|
564
|
+
recipient.emailType = upToDate.emailType;
|
|
565
|
+
recipient.objectId = item.objectId;
|
|
513
566
|
recipient.emailId = upToDate.id;
|
|
514
567
|
recipient.email = item.email;
|
|
515
568
|
recipient.firstName = item.firstName;
|
|
@@ -523,10 +576,6 @@ export class Email extends Model {
|
|
|
523
576
|
}
|
|
524
577
|
}
|
|
525
578
|
|
|
526
|
-
// todo: loop all members that match the filter in batches of 1000
|
|
527
|
-
// create a new row for every member + calculate the replacement values
|
|
528
|
-
// todo: do intermediate checks on whether the email was deleted, and stop processing if needed
|
|
529
|
-
|
|
530
579
|
upToDate.recipientsStatus = EmailRecipientsStatus.Created;
|
|
531
580
|
upToDate.recipientCount = count;
|
|
532
581
|
await upToDate.save();
|
|
@@ -578,7 +627,6 @@ export class Email extends Model {
|
|
|
578
627
|
});
|
|
579
628
|
|
|
580
629
|
while (request) {
|
|
581
|
-
console.log('Loading email page', subfilter.type, request);
|
|
582
630
|
const response = await loader.fetch(request, subfilter.subfilter);
|
|
583
631
|
|
|
584
632
|
// Note: it is possible that a result in the database doesn't return a recipient (in memory filtering)
|
|
@@ -613,32 +661,32 @@ export class Email extends Model {
|
|
|
613
661
|
}
|
|
614
662
|
|
|
615
663
|
async getPreviewStructure() {
|
|
616
|
-
const
|
|
617
|
-
.
|
|
618
|
-
.where(SQL.column('emailId'), this.id)
|
|
664
|
+
const emailRecipient = await EmailRecipient.select()
|
|
665
|
+
.where('emailId', this.id)
|
|
619
666
|
.first(false);
|
|
620
667
|
|
|
621
668
|
let recipientRow: EmailRecipientStruct | undefined;
|
|
622
669
|
|
|
623
|
-
if (
|
|
624
|
-
|
|
625
|
-
if (emailRecipient) {
|
|
626
|
-
recipientRow = emailRecipient.getStructure();
|
|
627
|
-
}
|
|
670
|
+
if (emailRecipient) {
|
|
671
|
+
recipientRow = emailRecipient.getStructure();
|
|
628
672
|
}
|
|
629
673
|
|
|
630
674
|
if (!recipientRow) {
|
|
631
675
|
recipientRow = getExampleRecipient();
|
|
632
676
|
}
|
|
633
677
|
|
|
634
|
-
const
|
|
635
|
-
|
|
678
|
+
const virtualRecipient = recipientRow.getRecipient();
|
|
679
|
+
|
|
680
|
+
await fillRecipientReplacements(virtualRecipient, {
|
|
681
|
+
organization: this.organizationId ? (await Organization.getByID(this.organizationId))! : null,
|
|
682
|
+
fromAddress: this.fromAddress,
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
recipientRow.replacements = virtualRecipient.replacements;
|
|
636
686
|
|
|
637
687
|
return EmailPreview.create({
|
|
638
688
|
...this,
|
|
639
689
|
exampleRecipient: recipientRow,
|
|
640
|
-
smartVariables,
|
|
641
|
-
smartButtons,
|
|
642
690
|
});
|
|
643
691
|
}
|
|
644
692
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { column
|
|
2
|
-
import { EmailRecipient as EmailRecipientStruct, Replacement } from '@stamhoofd/structures';
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import { EmailRecipient as EmailRecipientStruct, Recipient, Replacement } from '@stamhoofd/structures';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
import { ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
6
|
-
import {
|
|
6
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
7
7
|
|
|
8
|
-
export class EmailRecipient extends
|
|
8
|
+
export class EmailRecipient extends QueryableModel {
|
|
9
9
|
static table = 'email_recipients';
|
|
10
10
|
|
|
11
11
|
@column({
|
|
@@ -18,6 +18,20 @@ export class EmailRecipient extends Model {
|
|
|
18
18
|
@column({ type: 'string' })
|
|
19
19
|
emailId: string;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Helper to prevent sending too many emails to the same person.
|
|
23
|
+
* Allows for filtering on objects that didn't receive a specific email yet
|
|
24
|
+
*/
|
|
25
|
+
@column({ type: 'string', nullable: true })
|
|
26
|
+
objectId: string | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Helper to prevent sending too many emails to the same person.
|
|
30
|
+
* Allows for filtering on objects that didn't receive a specific email yet
|
|
31
|
+
*/
|
|
32
|
+
@column({ type: 'string', nullable: true })
|
|
33
|
+
emailType: string | null = null;
|
|
34
|
+
|
|
21
35
|
@column({ type: 'string', nullable: true })
|
|
22
36
|
firstName: string | null = null;
|
|
23
37
|
|
|
@@ -80,21 +94,7 @@ export class EmailRecipient extends Model {
|
|
|
80
94
|
return EmailRecipientStruct.create(this);
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
*/
|
|
86
|
-
static select() {
|
|
87
|
-
const transformer = (row: SQLResultNamespacedRow): EmailRecipient => {
|
|
88
|
-
const d = (this as typeof EmailRecipient & typeof Model).fromRow(row[this.table] as any) as EmailRecipient | undefined;
|
|
89
|
-
|
|
90
|
-
if (!d) {
|
|
91
|
-
throw new Error('EmailTemplate not found');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return d;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const select = new SQLSelect(transformer, SQL.wildcard());
|
|
98
|
-
return select.from(SQL.table(this.table));
|
|
97
|
+
getRecipient() {
|
|
98
|
+
return this.getStructure().getRecipient();
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
2
|
import { AnyDecoder } from '@simonbackx/simple-encoding';
|
|
3
|
-
import {
|
|
3
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
4
4
|
import { EmailTemplate as EmailTemplateStruct, EmailTemplateType } from '@stamhoofd/structures';
|
|
5
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Holds the challenges for a given email. User should not exist, since that would allow user enumeration attacks
|
|
9
9
|
*/
|
|
10
|
-
export class EmailTemplate extends
|
|
10
|
+
export class EmailTemplate extends QueryableModel {
|
|
11
11
|
static table = 'email_templates';
|
|
12
12
|
|
|
13
13
|
@column({
|
|
@@ -65,22 +65,4 @@ export class EmailTemplate extends Model {
|
|
|
65
65
|
getStructure() {
|
|
66
66
|
return EmailTemplateStruct.create(this);
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Experimental: needs to move to library
|
|
71
|
-
*/
|
|
72
|
-
static select() {
|
|
73
|
-
const transformer = (row: SQLResultNamespacedRow): EmailTemplate => {
|
|
74
|
-
const d = (this as typeof EmailTemplate & typeof Model).fromRow(row[this.table] as any) as EmailTemplate | undefined;
|
|
75
|
-
|
|
76
|
-
if (!d) {
|
|
77
|
-
throw new Error('EmailTemplate not found');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return d;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const select = new SQLSelect(transformer, SQL.wildcard());
|
|
84
|
-
return select.from(SQL.table(this.table));
|
|
85
|
-
}
|
|
86
68
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
2
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
3
3
|
import { I18n } from '@stamhoofd/backend-i18n';
|
|
4
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
4
5
|
import { EmailTemplateType, Recipient, Replacement } from '@stamhoofd/structures';
|
|
5
6
|
import basex from 'base-x';
|
|
6
7
|
import crypto from 'crypto';
|
|
@@ -38,7 +39,7 @@ async function randomInt(max: number): Promise<number> {
|
|
|
38
39
|
/**
|
|
39
40
|
* Holds the verificationCodes for a given email (not a user, since a user can switch email addresses and might avoid verification that way)
|
|
40
41
|
*/
|
|
41
|
-
export class EmailVerificationCode extends
|
|
42
|
+
export class EmailVerificationCode extends QueryableModel {
|
|
42
43
|
static table = 'email_verification_codes';
|
|
43
44
|
|
|
44
45
|
@column({
|
package/src/models/Event.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
2
3
|
import { EventMeta, Event as EventStruct, GroupType } from '@stamhoofd/structures';
|
|
3
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
5
|
import { Group } from './Group';
|
|
5
6
|
|
|
6
|
-
export class Event extends
|
|
7
|
+
export class Event extends QueryableModel {
|
|
7
8
|
static table = 'events';
|
|
8
9
|
|
|
9
10
|
@column({ primary: true, type: 'string', beforeSave(value) {
|
package/src/models/Group.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { column, Database, ManyToOneRelation,
|
|
1
|
+
import { column, Database, ManyToOneRelation, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
2
|
import { GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType, StockReservation } from '@stamhoofd/structures';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
import { ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
6
6
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
7
|
-
import {
|
|
7
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
8
8
|
import { Formatter } from '@stamhoofd/utility';
|
|
9
9
|
import { Member, MemberWithRegistrations, OrganizationRegistrationPeriod, Payment, Registration, User } from './';
|
|
10
10
|
|
|
@@ -21,7 +21,7 @@ if (Registration === undefined) {
|
|
|
21
21
|
throw new Error('Import Registration is undefined');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export class Group extends
|
|
24
|
+
export class Group extends QueryableModel {
|
|
25
25
|
static table = 'groups';
|
|
26
26
|
|
|
27
27
|
@column({
|
|
@@ -100,10 +100,9 @@ export class Group extends Model {
|
|
|
100
100
|
static async getAll(organizationId: string, periodId: string | null, active = true) {
|
|
101
101
|
const w: any = periodId ? { periodId } : {};
|
|
102
102
|
if (active) {
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
104
103
|
return await Group.where({ organizationId, deletedAt: null, ...w });
|
|
105
104
|
}
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
return await Group.where({ organizationId, ...w });
|
|
108
107
|
}
|
|
109
108
|
|
|
@@ -299,24 +298,6 @@ export class Group extends Model {
|
|
|
299
298
|
static async freeStockReservations(groupId: string, reservations: StockReservation[]) {
|
|
300
299
|
return await this.applyStockReservations(groupId, reservations, true);
|
|
301
300
|
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Experimental: needs to move to library
|
|
305
|
-
*/
|
|
306
|
-
static select() {
|
|
307
|
-
const transformer = (row: SQLResultNamespacedRow): Group => {
|
|
308
|
-
const d = (this as typeof Group & typeof Model).fromRow(row[this.table] as any) as Group | undefined;
|
|
309
|
-
|
|
310
|
-
if (!d) {
|
|
311
|
-
throw new Error('EmailTemplate not found');
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return d;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const select = new SQLSelect(transformer, SQL.wildcard());
|
|
318
|
-
return select.from(SQL.table(this.table));
|
|
319
|
-
}
|
|
320
301
|
}
|
|
321
302
|
|
|
322
303
|
Registration.group = new ManyToOneRelation(Group, 'group');
|
package/src/models/Image.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { column
|
|
1
|
+
import { column } from '@simonbackx/simple-database';
|
|
2
2
|
import { ArrayDecoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { QueryableModel } from '@stamhoofd/sql';
|
|
4
5
|
import { File, Resolution, ResolutionRequest } from '@stamhoofd/structures';
|
|
5
6
|
import AWS from 'aws-sdk';
|
|
6
7
|
import sharp from 'sharp';
|
|
7
8
|
import { v4 as uuidv4 } from 'uuid';
|
|
8
9
|
|
|
9
|
-
export class Image extends
|
|
10
|
+
export class Image extends QueryableModel {
|
|
10
11
|
static table = 'images';
|
|
11
12
|
|
|
12
13
|
@column({ primary: true, type: 'string', beforeSave(value) {
|
package/src/models/Member.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { column, Database, ManyToManyRelation, ManyToOneRelation,
|
|
2
|
-
import { SQL } from '@stamhoofd/sql';
|
|
3
|
-
import { MemberDetails,
|
|
1
|
+
import { column, Database, ManyToManyRelation, ManyToOneRelation, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
|
+
import { QueryableModel, SQL } from '@stamhoofd/sql';
|
|
3
|
+
import { MemberDetails, RegistrationWithMember as RegistrationWithMemberStruct, TinyMember } from '@stamhoofd/structures';
|
|
4
4
|
import { Formatter } from '@stamhoofd/utility';
|
|
5
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ export type MemberWithRegistrations = Member & {
|
|
|
13
13
|
// Defined here to prevent cycles
|
|
14
14
|
export type RegistrationWithMember = Registration & { member: Member };
|
|
15
15
|
|
|
16
|
-
export class Member extends
|
|
16
|
+
export class Member extends QueryableModel {
|
|
17
17
|
static table = 'members';
|
|
18
18
|
|
|
19
19
|
// #region Columns
|
|
@@ -63,7 +63,8 @@ export class Member extends Model {
|
|
|
63
63
|
details: MemberDetails;
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* @deprecated
|
|
67
|
+
* Unreliable since a member can have outstanding balance to multiple organizations now
|
|
67
68
|
*/
|
|
68
69
|
@column({ type: 'integer' })
|
|
69
70
|
outstandingBalance = 0;
|
|
@@ -103,44 +104,6 @@ export class Member extends Model {
|
|
|
103
104
|
return (await this.getBlobByIds(id))[0] ?? null;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
/**
|
|
107
|
-
* Update the outstanding balance of multiple members in one go (or all members)
|
|
108
|
-
*/
|
|
109
|
-
static async updateOutstandingBalance(memberIds: string[] | 'all') {
|
|
110
|
-
if (memberIds !== 'all' && memberIds.length == 0) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const params: any[] = [];
|
|
115
|
-
let firstWhere = '';
|
|
116
|
-
let secondWhere = '';
|
|
117
|
-
|
|
118
|
-
if (memberIds !== 'all') {
|
|
119
|
-
firstWhere = ` AND memberId IN (?)`;
|
|
120
|
-
params.push(memberIds);
|
|
121
|
-
|
|
122
|
-
secondWhere = `WHERE members.id IN (?)`;
|
|
123
|
-
params.push(memberIds);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const query = `UPDATE
|
|
127
|
-
members
|
|
128
|
-
LEFT JOIN (
|
|
129
|
-
SELECT
|
|
130
|
-
memberId,
|
|
131
|
-
sum(unitPrice * amount) - sum(pricePaid) AS outstandingBalance
|
|
132
|
-
FROM
|
|
133
|
-
balance_items
|
|
134
|
-
WHERE status != 'Hidden'${firstWhere}
|
|
135
|
-
GROUP BY
|
|
136
|
-
memberId
|
|
137
|
-
) i ON i.memberId = members.id
|
|
138
|
-
SET members.outstandingBalance = COALESCE(i.outstandingBalance, 0)
|
|
139
|
-
${secondWhere}`;
|
|
140
|
-
|
|
141
|
-
await Database.update(query, params);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
107
|
/**
|
|
145
108
|
* Fetch all registrations with members with their corresponding (valid) registrations
|
|
146
109
|
*/
|
|
@@ -377,15 +340,6 @@ export class Member extends Model {
|
|
|
377
340
|
return this.getBlobByIds(...(await this.getMemberIdsWithRegistrationForUser(user)));
|
|
378
341
|
}
|
|
379
342
|
|
|
380
|
-
getStructureWithRegistrations(this: MemberWithRegistrations, forOrganization: null | boolean = null) {
|
|
381
|
-
return MemberWithRegistrationsBlob.create({
|
|
382
|
-
...this,
|
|
383
|
-
registrations: this.registrations.map(r => r.getStructure()),
|
|
384
|
-
details: this.details,
|
|
385
|
-
users: this.users.map(u => u.getStructure()),
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
343
|
static getRegistrationWithMemberStructure(registration: RegistrationWithMember & { group: import('./Group').Group }): RegistrationWithMemberStruct {
|
|
390
344
|
return RegistrationWithMemberStruct.create({
|
|
391
345
|
...registration.getStructure(),
|