@stamhoofd/models 2.1.1
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/assets/Metropolis-Black.woff2 +0 -0
- package/dist/src/assets/Metropolis-BlackItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-Bold.woff2 +0 -0
- package/dist/src/assets/Metropolis-BoldItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-ExtraBold.woff2 +0 -0
- package/dist/src/assets/Metropolis-ExtraBoldItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-ExtraLight.woff2 +0 -0
- package/dist/src/assets/Metropolis-ExtraLightItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-Light.woff2 +0 -0
- package/dist/src/assets/Metropolis-LightItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-Medium.woff2 +0 -0
- package/dist/src/assets/Metropolis-MediumItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-Regular.woff2 +0 -0
- package/dist/src/assets/Metropolis-RegularItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-SemiBold.woff2 +0 -0
- package/dist/src/assets/Metropolis-SemiBoldItalic.woff2 +0 -0
- package/dist/src/assets/Metropolis-Thin.woff2 +0 -0
- package/dist/src/assets/Metropolis-ThinItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Black.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-BlackItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Bold.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-BoldItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraBold.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraBoldItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraLight.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ExtraLightItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Light.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-LightItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Medium.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-MediumItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Regular.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-RegularItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-SemiBold.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-SemiBoldItalic.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-Thin.woff2 +0 -0
- package/dist/src/assets/assets/Metropolis-ThinItalic.woff2 +0 -0
- package/dist/src/assets/assets/logo.png +0 -0
- package/dist/src/assets/logo.png +0 -0
- package/dist/src/factories/AddressFactory.d.ts +10 -0
- package/dist/src/factories/AddressFactory.d.ts.map +1 -0
- package/dist/src/factories/AddressFactory.js +42 -0
- package/dist/src/factories/AddressFactory.js.map +1 -0
- package/dist/src/factories/EmergencyContactFactory.d.ts +9 -0
- package/dist/src/factories/EmergencyContactFactory.d.ts.map +1 -0
- package/dist/src/factories/EmergencyContactFactory.js +42 -0
- package/dist/src/factories/EmergencyContactFactory.js.map +1 -0
- package/dist/src/factories/GroupFactory.d.ts +19 -0
- package/dist/src/factories/GroupFactory.d.ts.map +1 -0
- package/dist/src/factories/GroupFactory.js +52 -0
- package/dist/src/factories/GroupFactory.js.map +1 -0
- package/dist/src/factories/MemberFactory.d.ts +16 -0
- package/dist/src/factories/MemberFactory.d.ts.map +1 -0
- package/dist/src/factories/MemberFactory.js +98 -0
- package/dist/src/factories/MemberFactory.js.map +1 -0
- package/dist/src/factories/OrganizationFactory.d.ts +16 -0
- package/dist/src/factories/OrganizationFactory.d.ts.map +1 -0
- package/dist/src/factories/OrganizationFactory.js +40 -0
- package/dist/src/factories/OrganizationFactory.js.map +1 -0
- package/dist/src/factories/ParentFactory.d.ts +10 -0
- package/dist/src/factories/ParentFactory.d.ts.map +1 -0
- package/dist/src/factories/ParentFactory.js +44 -0
- package/dist/src/factories/ParentFactory.js.map +1 -0
- package/dist/src/factories/RecordFactory.d.ts +10 -0
- package/dist/src/factories/RecordFactory.d.ts.map +1 -0
- package/dist/src/factories/RecordFactory.js +14 -0
- package/dist/src/factories/RecordFactory.js.map +1 -0
- package/dist/src/factories/RegisterCodeFactory.d.ts +18 -0
- package/dist/src/factories/RegisterCodeFactory.d.ts.map +1 -0
- package/dist/src/factories/RegisterCodeFactory.js +19 -0
- package/dist/src/factories/RegisterCodeFactory.js.map +1 -0
- package/dist/src/factories/RegistrationFactory.d.ts +14 -0
- package/dist/src/factories/RegistrationFactory.d.ts.map +1 -0
- package/dist/src/factories/RegistrationFactory.js +26 -0
- package/dist/src/factories/RegistrationFactory.js.map +1 -0
- package/dist/src/factories/UserFactory.d.ts +21 -0
- package/dist/src/factories/UserFactory.d.ts.map +1 -0
- package/dist/src/factories/UserFactory.js +41 -0
- package/dist/src/factories/UserFactory.js.map +1 -0
- package/dist/src/factories/WebshopFactory.d.ts +16 -0
- package/dist/src/factories/WebshopFactory.d.ts.map +1 -0
- package/dist/src/factories/WebshopFactory.js +35 -0
- package/dist/src/factories/WebshopFactory.js.map +1 -0
- package/dist/src/helpers/DNSValidator.d.ts +6 -0
- package/dist/src/helpers/DNSValidator.d.ts.map +1 -0
- package/dist/src/helpers/DNSValidator.js +144 -0
- package/dist/src/helpers/DNSValidator.js.map +1 -0
- package/dist/src/helpers/EmailBuilder.d.ts +22 -0
- package/dist/src/helpers/EmailBuilder.d.ts.map +1 -0
- package/dist/src/helpers/EmailBuilder.js +100 -0
- package/dist/src/helpers/EmailBuilder.js.map +1 -0
- package/dist/src/helpers/GroupBuilder.d.ts +9 -0
- package/dist/src/helpers/GroupBuilder.d.ts.map +1 -0
- package/dist/src/helpers/GroupBuilder.js +382 -0
- package/dist/src/helpers/GroupBuilder.js.map +1 -0
- package/dist/src/helpers/Handlebars.d.ts +2 -0
- package/dist/src/helpers/Handlebars.d.ts.map +1 -0
- package/dist/src/helpers/Handlebars.js +192 -0
- package/dist/src/helpers/Handlebars.js.map +1 -0
- package/dist/src/helpers/InvoiceBuilder.d.ts +29 -0
- package/dist/src/helpers/InvoiceBuilder.d.ts.map +1 -0
- package/dist/src/helpers/InvoiceBuilder.js +406 -0
- package/dist/src/helpers/InvoiceBuilder.js.map +1 -0
- package/dist/src/helpers/InvoiceBuilder.test.d.ts +2 -0
- package/dist/src/helpers/InvoiceBuilder.test.d.ts.map +1 -0
- package/dist/src/helpers/InvoiceBuilder.test.js +52 -0
- package/dist/src/helpers/InvoiceBuilder.test.js.map +1 -0
- package/dist/src/helpers/RateLimiter.d.ts +27 -0
- package/dist/src/helpers/RateLimiter.d.ts.map +1 -0
- package/dist/src/helpers/RateLimiter.js +57 -0
- package/dist/src/helpers/RateLimiter.js.map +1 -0
- package/dist/src/helpers/WebshopCounter.d.ts +6 -0
- package/dist/src/helpers/WebshopCounter.d.ts.map +1 -0
- package/dist/src/helpers/WebshopCounter.js +36 -0
- package/dist/src/helpers/WebshopCounter.js.map +1 -0
- package/dist/src/helpers/WebshopCounter.test.d.ts +2 -0
- package/dist/src/helpers/WebshopCounter.test.d.ts.map +1 -0
- package/dist/src/helpers/WebshopCounter.test.js +17 -0
- package/dist/src/helpers/WebshopCounter.test.js.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +23 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/migrations/1593773929-create-initial-tables.sql +634 -0
- package/dist/src/migrations/1605261999-gemeenten-tmp.sql +2820 -0
- package/dist/src/migrations/1605262045-import-postcodes.d.ts +16 -0
- package/dist/src/migrations/1605262045-import-postcodes.d.ts.map +1 -0
- package/dist/src/migrations/1605262045-import-postcodes.js +116 -0
- package/dist/src/migrations/1605262045-import-postcodes.js.map +1 -0
- package/dist/src/migrations/1605262046-import-postcodes-nl.d.ts +4 -0
- package/dist/src/migrations/1605262046-import-postcodes-nl.d.ts.map +1 -0
- package/dist/src/migrations/1605262046-import-postcodes-nl.js +83 -0
- package/dist/src/migrations/1605262046-import-postcodes-nl.js.map +1 -0
- package/dist/src/migrations/1605279038-drop-gemeenten-tmp.sql +1 -0
- package/dist/src/migrations/1648392491-default-templates.sql +9 -0
- package/dist/src/migrations/1651245707-default-templates-reminders.sql +6 -0
- package/dist/src/migrations/1708607340-tickets-deleted-at.sql +1 -0
- package/dist/src/migrations/1710459176-register-code-invoices.sql +3 -0
- package/dist/src/migrations/1712158247-discount-codes.sql +17 -0
- package/dist/src/migrations/1713178665-drop-invites.sql +1 -0
- package/dist/src/migrations/1713178666-drop-keychain.sql +1 -0
- package/dist/src/migrations/1713178667-drop-challenges.sql +1 -0
- package/dist/src/migrations/1713178668-drop-user-keys.sql +7 -0
- package/dist/src/migrations/1713178669-drop-organization-key.sql +2 -0
- package/dist/src/migrations/data/postcodes/nl/Drenthe +291 -0
- package/dist/src/migrations/data/postcodes/nl/Flevoland +107 -0
- package/dist/src/migrations/data/postcodes/nl/Friesland +518 -0
- package/dist/src/migrations/data/postcodes/nl/Gelderland +601 -0
- package/dist/src/migrations/data/postcodes/nl/Groningen +279 -0
- package/dist/src/migrations/data/postcodes/nl/Limburg +324 -0
- package/dist/src/migrations/data/postcodes/nl/Noord-Brabant +620 -0
- package/dist/src/migrations/data/postcodes/nl/Noord-Holland +566 -0
- package/dist/src/migrations/data/postcodes/nl/Overrijsel +344 -0
- package/dist/src/migrations/data/postcodes/nl/Utrecht +278 -0
- package/dist/src/migrations/data/postcodes/nl/Zeeland +179 -0
- package/dist/src/migrations/data/postcodes/nl/Zuid-Holland +662 -0
- package/dist/src/models/BalanceItem.d.ts +57 -0
- package/dist/src/models/BalanceItem.d.ts.map +1 -0
- package/dist/src/models/BalanceItem.js +346 -0
- package/dist/src/models/BalanceItem.js.map +1 -0
- package/dist/src/models/BalanceItemPayment.d.ts +31 -0
- package/dist/src/models/BalanceItemPayment.d.ts.map +1 -0
- package/dist/src/models/BalanceItemPayment.js +101 -0
- package/dist/src/models/BalanceItemPayment.js.map +1 -0
- package/dist/src/models/BuckarooPayment.d.ts +8 -0
- package/dist/src/models/BuckarooPayment.d.ts.map +1 -0
- package/dist/src/models/BuckarooPayment.js +24 -0
- package/dist/src/models/BuckarooPayment.js.map +1 -0
- package/dist/src/models/Document.d.ts +44 -0
- package/dist/src/models/Document.d.ts.map +1 -0
- package/dist/src/models/Document.js +194 -0
- package/dist/src/models/Document.js.map +1 -0
- package/dist/src/models/DocumentTemplate.d.ts +45 -0
- package/dist/src/models/DocumentTemplate.d.ts.map +1 -0
- package/dist/src/models/DocumentTemplate.js +533 -0
- package/dist/src/models/DocumentTemplate.js.map +1 -0
- package/dist/src/models/EmailTemplate.d.ts +22 -0
- package/dist/src/models/EmailTemplate.d.ts.map +1 -0
- package/dist/src/models/EmailTemplate.js +70 -0
- package/dist/src/models/EmailTemplate.js.map +1 -0
- package/dist/src/models/EmailVerificationCode.d.ts +60 -0
- package/dist/src/models/EmailVerificationCode.d.ts.map +1 -0
- package/dist/src/models/EmailVerificationCode.js +307 -0
- package/dist/src/models/EmailVerificationCode.js.map +1 -0
- package/dist/src/models/Group.d.ts +36 -0
- package/dist/src/models/Group.d.ts.map +1 -0
- package/dist/src/models/Group.js +231 -0
- package/dist/src/models/Group.js.map +1 -0
- package/dist/src/models/Image.d.ts +12 -0
- package/dist/src/models/Image.d.ts.map +1 -0
- package/dist/src/models/Image.js +137 -0
- package/dist/src/models/Image.js.map +1 -0
- package/dist/src/models/Member.d.ts +66 -0
- package/dist/src/models/Member.d.ts.map +1 -0
- package/dist/src/models/Member.js +309 -0
- package/dist/src/models/Member.js.map +1 -0
- package/dist/src/models/MemberResponsibilityRecord.d.ts +11 -0
- package/dist/src/models/MemberResponsibilityRecord.d.ts.map +1 -0
- package/dist/src/models/MemberResponsibilityRecord.js +47 -0
- package/dist/src/models/MemberResponsibilityRecord.js.map +1 -0
- package/dist/src/models/MolliePayment.d.ts +8 -0
- package/dist/src/models/MolliePayment.d.ts.map +1 -0
- package/dist/src/models/MolliePayment.js +24 -0
- package/dist/src/models/MolliePayment.js.map +1 -0
- package/dist/src/models/MollieToken.d.ts +45 -0
- package/dist/src/models/MollieToken.d.ts.map +1 -0
- package/dist/src/models/MollieToken.js +333 -0
- package/dist/src/models/MollieToken.js.map +1 -0
- package/dist/src/models/OneTimeToken.d.ts +38 -0
- package/dist/src/models/OneTimeToken.d.ts.map +1 -0
- package/dist/src/models/OneTimeToken.js +126 -0
- package/dist/src/models/OneTimeToken.js.map +1 -0
- package/dist/src/models/Order.d.ts +99 -0
- package/dist/src/models/Order.d.ts.map +1 -0
- package/dist/src/models/Order.js +912 -0
- package/dist/src/models/Order.js.map +1 -0
- package/dist/src/models/Organization.d.ts +119 -0
- package/dist/src/models/Organization.d.ts.map +1 -0
- package/dist/src/models/Organization.js +900 -0
- package/dist/src/models/Organization.js.map +1 -0
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts +14 -0
- package/dist/src/models/OrganizationRegistrationPeriod.d.ts.map +1 -0
- package/dist/src/models/OrganizationRegistrationPeriod.js +62 -0
- package/dist/src/models/OrganizationRegistrationPeriod.js.map +1 -0
- package/dist/src/models/PasswordToken.d.ts +29 -0
- package/dist/src/models/PasswordToken.d.ts.map +1 -0
- package/dist/src/models/PasswordToken.js +118 -0
- package/dist/src/models/PasswordToken.js.map +1 -0
- package/dist/src/models/PayconiqPayment.d.ts +18 -0
- package/dist/src/models/PayconiqPayment.d.ts.map +1 -0
- package/dist/src/models/PayconiqPayment.js +216 -0
- package/dist/src/models/PayconiqPayment.js.map +1 -0
- package/dist/src/models/Payment.d.ts +62 -0
- package/dist/src/models/Payment.d.ts.map +1 -0
- package/dist/src/models/Payment.js +199 -0
- package/dist/src/models/Payment.js.map +1 -0
- package/dist/src/models/Platform.d.ts +15 -0
- package/dist/src/models/Platform.d.ts.map +1 -0
- package/dist/src/models/Platform.js +77 -0
- package/dist/src/models/Platform.js.map +1 -0
- package/dist/src/models/RegisterCode.d.ts +27 -0
- package/dist/src/models/RegisterCode.d.ts.map +1 -0
- package/dist/src/models/RegisterCode.js +162 -0
- package/dist/src/models/RegisterCode.js.map +1 -0
- package/dist/src/models/Registration.d.ts +47 -0
- package/dist/src/models/Registration.d.ts.map +1 -0
- package/dist/src/models/Registration.js +369 -0
- package/dist/src/models/Registration.js.map +1 -0
- package/dist/src/models/RegistrationPeriod.d.ts +15 -0
- package/dist/src/models/RegistrationPeriod.d.ts.map +1 -0
- package/dist/src/models/RegistrationPeriod.js +64 -0
- package/dist/src/models/RegistrationPeriod.js.map +1 -0
- package/dist/src/models/STCredit.d.ts +20 -0
- package/dist/src/models/STCredit.d.ts.map +1 -0
- package/dist/src/models/STCredit.js +129 -0
- package/dist/src/models/STCredit.js.map +1 -0
- package/dist/src/models/STInvoice.d.ts +51 -0
- package/dist/src/models/STInvoice.d.ts.map +1 -0
- package/dist/src/models/STInvoice.js +453 -0
- package/dist/src/models/STInvoice.js.map +1 -0
- package/dist/src/models/STPackage.d.ts +36 -0
- package/dist/src/models/STPackage.d.ts.map +1 -0
- package/dist/src/models/STPackage.js +300 -0
- package/dist/src/models/STPackage.js.map +1 -0
- package/dist/src/models/STPendingInvoice.d.ts +45 -0
- package/dist/src/models/STPendingInvoice.d.ts.map +1 -0
- package/dist/src/models/STPendingInvoice.js +284 -0
- package/dist/src/models/STPendingInvoice.js.map +1 -0
- package/dist/src/models/StripeAccount.d.ts +17 -0
- package/dist/src/models/StripeAccount.d.ts.map +1 -0
- package/dist/src/models/StripeAccount.js +81 -0
- package/dist/src/models/StripeAccount.js.map +1 -0
- package/dist/src/models/StripeCheckoutSession.d.ts +9 -0
- package/dist/src/models/StripeCheckoutSession.d.ts.map +1 -0
- package/dist/src/models/StripeCheckoutSession.js +31 -0
- package/dist/src/models/StripeCheckoutSession.js.map +1 -0
- package/dist/src/models/StripePaymentIntent.d.ts +9 -0
- package/dist/src/models/StripePaymentIntent.d.ts.map +1 -0
- package/dist/src/models/StripePaymentIntent.js +31 -0
- package/dist/src/models/StripePaymentIntent.js.map +1 -0
- package/dist/src/models/Ticket.d.ts +61 -0
- package/dist/src/models/Ticket.d.ts.map +1 -0
- package/dist/src/models/Ticket.js +143 -0
- package/dist/src/models/Ticket.js.map +1 -0
- package/dist/src/models/Token.d.ts +49 -0
- package/dist/src/models/Token.d.ts.map +1 -0
- package/dist/src/models/Token.js +218 -0
- package/dist/src/models/Token.js.map +1 -0
- package/dist/src/models/Token.test.d.ts +2 -0
- package/dist/src/models/Token.test.d.ts.map +1 -0
- package/dist/src/models/Token.test.js +60 -0
- package/dist/src/models/Token.test.js.map +1 -0
- package/dist/src/models/UsedRegisterCode.d.ts +22 -0
- package/dist/src/models/UsedRegisterCode.d.ts.map +1 -0
- package/dist/src/models/UsedRegisterCode.js +158 -0
- package/dist/src/models/UsedRegisterCode.js.map +1 -0
- package/dist/src/models/User.d.ts +55 -0
- package/dist/src/models/User.d.ts.map +1 -0
- package/dist/src/models/User.js +314 -0
- package/dist/src/models/User.js.map +1 -0
- package/dist/src/models/UserPermissions.d.ts +15 -0
- package/dist/src/models/UserPermissions.d.ts.map +1 -0
- package/dist/src/models/UserPermissions.js +57 -0
- package/dist/src/models/UserPermissions.js.map +1 -0
- package/dist/src/models/Webshop.d.ts +44 -0
- package/dist/src/models/Webshop.d.ts.map +1 -0
- package/dist/src/models/Webshop.js +184 -0
- package/dist/src/models/Webshop.js.map +1 -0
- package/dist/src/models/WebshopDiscountCode.d.ts +18 -0
- package/dist/src/models/WebshopDiscountCode.d.ts.map +1 -0
- package/dist/src/models/WebshopDiscountCode.js +88 -0
- package/dist/src/models/WebshopDiscountCode.js.map +1 -0
- package/dist/src/models/addresses/City.d.ts +14 -0
- package/dist/src/models/addresses/City.d.ts.map +1 -0
- package/dist/src/models/addresses/City.js +37 -0
- package/dist/src/models/addresses/City.js.map +1 -0
- package/dist/src/models/addresses/PostalCode.d.ts +20 -0
- package/dist/src/models/addresses/PostalCode.d.ts.map +1 -0
- package/dist/src/models/addresses/PostalCode.js +138 -0
- package/dist/src/models/addresses/PostalCode.js.map +1 -0
- package/dist/src/models/addresses/PostalCode.test.d.ts +2 -0
- package/dist/src/models/addresses/PostalCode.test.d.ts.map +1 -0
- package/dist/src/models/addresses/PostalCode.test.js +98 -0
- package/dist/src/models/addresses/PostalCode.test.js.map +1 -0
- package/dist/src/models/addresses/Province.d.ts +9 -0
- package/dist/src/models/addresses/Province.d.ts.map +1 -0
- package/dist/src/models/addresses/Province.js +24 -0
- package/dist/src/models/addresses/Province.js.map +1 -0
- package/dist/src/models/addresses/Street.d.ts +10 -0
- package/dist/src/models/addresses/Street.d.ts.map +1 -0
- package/dist/src/models/addresses/Street.js +26 -0
- package/dist/src/models/addresses/Street.js.map +1 -0
- package/dist/src/models/index.d.ts +37 -0
- package/dist/src/models/index.d.ts.map +1 -0
- package/dist/src/models/index.js +53 -0
- package/dist/src/models/index.js.map +1 -0
- package/dist/src/structures/OrganizationServerMetaData.d.ts +43 -0
- package/dist/src/structures/OrganizationServerMetaData.d.ts.map +1 -0
- package/dist/src/structures/OrganizationServerMetaData.js +128 -0
- package/dist/src/structures/OrganizationServerMetaData.js.map +1 -0
- package/dist/tests/jest.global.setup.d.ts +3 -0
- package/dist/tests/jest.global.setup.d.ts.map +1 -0
- package/dist/tests/jest.global.setup.js +20 -0
- package/dist/tests/jest.global.setup.js.map +1 -0
- package/dist/tests/jest.setup.d.ts +2 -0
- package/dist/tests/jest.setup.d.ts.map +1 -0
- package/dist/tests/jest.setup.js +15 -0
- package/dist/tests/jest.setup.js.map +1 -0
- package/package.json +30 -0
- package/src/assets/Metropolis-Black.woff2 +0 -0
- package/src/assets/Metropolis-BlackItalic.woff2 +0 -0
- package/src/assets/Metropolis-Bold.woff2 +0 -0
- package/src/assets/Metropolis-BoldItalic.woff2 +0 -0
- package/src/assets/Metropolis-ExtraBold.woff2 +0 -0
- package/src/assets/Metropolis-ExtraBoldItalic.woff2 +0 -0
- package/src/assets/Metropolis-ExtraLight.woff2 +0 -0
- package/src/assets/Metropolis-ExtraLightItalic.woff2 +0 -0
- package/src/assets/Metropolis-Light.woff2 +0 -0
- package/src/assets/Metropolis-LightItalic.woff2 +0 -0
- package/src/assets/Metropolis-Medium.woff2 +0 -0
- package/src/assets/Metropolis-MediumItalic.woff2 +0 -0
- package/src/assets/Metropolis-Regular.woff2 +0 -0
- package/src/assets/Metropolis-RegularItalic.woff2 +0 -0
- package/src/assets/Metropolis-SemiBold.woff2 +0 -0
- package/src/assets/Metropolis-SemiBoldItalic.woff2 +0 -0
- package/src/assets/Metropolis-Thin.woff2 +0 -0
- package/src/assets/Metropolis-ThinItalic.woff2 +0 -0
- package/src/assets/logo.png +0 -0
- package/src/factories/AddressFactory.ts +42 -0
- package/src/factories/EmergencyContactFactory.ts +43 -0
- package/src/factories/GroupFactory.ts +66 -0
- package/src/factories/MemberFactory.ts +122 -0
- package/src/factories/OrganizationFactory.ts +45 -0
- package/src/factories/ParentFactory.ts +49 -0
- package/src/factories/RecordFactory.ts +12 -0
- package/src/factories/RegisterCodeFactory.ts +25 -0
- package/src/factories/RegistrationFactory.ts +32 -0
- package/src/factories/UserFactory.ts +66 -0
- package/src/factories/WebshopFactory.ts +43 -0
- package/src/helpers/DNSValidator.ts +153 -0
- package/src/helpers/EmailBuilder.ts +127 -0
- package/src/helpers/GroupBuilder.ts +438 -0
- package/src/helpers/Handlebars.ts +203 -0
- package/src/helpers/InvoiceBuilder.test.ts +57 -0
- package/src/helpers/InvoiceBuilder.ts +501 -0
- package/src/helpers/RateLimiter.ts +75 -0
- package/src/helpers/WebshopCounter.test.ts +16 -0
- package/src/helpers/WebshopCounter.ts +36 -0
- package/src/index.ts +21 -0
- package/src/migrations/1593773929-create-initial-tables.sql +634 -0
- package/src/migrations/1605261999-gemeenten-tmp.sql +2820 -0
- package/src/migrations/1605262045-import-postcodes.ts +132 -0
- package/src/migrations/1605262046-import-postcodes-nl.ts +97 -0
- package/src/migrations/1605279038-drop-gemeenten-tmp.sql +1 -0
- package/src/migrations/1648392491-default-templates.sql +9 -0
- package/src/migrations/1651245707-default-templates-reminders.sql +6 -0
- package/src/migrations/1708607340-tickets-deleted-at.sql +1 -0
- package/src/migrations/1710459176-register-code-invoices.sql +3 -0
- package/src/migrations/1712158247-discount-codes.sql +17 -0
- package/src/migrations/1713178665-drop-invites.sql +1 -0
- package/src/migrations/1713178666-drop-keychain.sql +1 -0
- package/src/migrations/1713178667-drop-challenges.sql +1 -0
- package/src/migrations/1713178668-drop-user-keys.sql +7 -0
- package/src/migrations/1713178669-drop-organization-key.sql +2 -0
- package/src/migrations/1714985451-user-nullable-organization-id.sql +2 -0
- package/src/migrations/1714985452-email-verification-code-nullable-organization-id.sql +2 -0
- package/src/migrations/1714985453-user-organization-permissions.sql +2 -0
- package/src/migrations/1714985454-user-organization-permissions.sql +2 -0
- package/src/migrations/1715079362-platform.sql +6 -0
- package/src/migrations/1715181649-registrations-organization-id.sql +2 -0
- package/src/migrations/1715181650-registrations-organization-id-fill.sql +1 -0
- package/src/migrations/1715181651-registrations-organization-id-drop-null.sql +2 -0
- package/src/migrations/1716117067-members-nullable-organization-id.sql +2 -0
- package/src/migrations/1719567581-registration-periods.sql +13 -0
- package/src/migrations/1719567582-organization-registration-periods.sql +13 -0
- package/src/migrations/1719567881-organization-periodId.sql +2 -0
- package/src/migrations/1719567882-groups-periodId.sql +2 -0
- package/src/migrations/1719567883-platform-periodId.sql +2 -0
- package/src/migrations/1719568079-default-period.sql +2 -0
- package/src/migrations/1719568080-set-default-period-platform.sql +1 -0
- package/src/migrations/1719568081-set-default-period-organizations.sql +1 -0
- package/src/migrations/1719568082-set-default-period-groups.sql +1 -0
- package/src/migrations/1719580828-registrations-periodId.sql +2 -0
- package/src/migrations/1719580829-set-default-period-registrations.sql +1 -0
- package/src/migrations/data/postcodes/nl/Drenthe +291 -0
- package/src/migrations/data/postcodes/nl/Flevoland +107 -0
- package/src/migrations/data/postcodes/nl/Friesland +518 -0
- package/src/migrations/data/postcodes/nl/Gelderland +601 -0
- package/src/migrations/data/postcodes/nl/Groningen +279 -0
- package/src/migrations/data/postcodes/nl/Limburg +324 -0
- package/src/migrations/data/postcodes/nl/Noord-Brabant +620 -0
- package/src/migrations/data/postcodes/nl/Noord-Holland +566 -0
- package/src/migrations/data/postcodes/nl/Overrijsel +344 -0
- package/src/migrations/data/postcodes/nl/Utrecht +278 -0
- package/src/migrations/data/postcodes/nl/Zeeland +179 -0
- package/src/migrations/data/postcodes/nl/Zuid-Holland +662 -0
- package/src/models/BalanceItem.ts +392 -0
- package/src/models/BalanceItemPayment.ts +106 -0
- package/src/models/BuckarooPayment.ts +19 -0
- package/src/models/Document.ts +203 -0
- package/src/models/DocumentTemplate.ts +583 -0
- package/src/models/EmailTemplate.ts +64 -0
- package/src/models/EmailVerificationCode.ts +352 -0
- package/src/models/Group.ts +293 -0
- package/src/models/Image.ts +147 -0
- package/src/models/Member.ts +386 -0
- package/src/models/MemberResponsibilityRecord.ts +39 -0
- package/src/models/MolliePayment.ts +19 -0
- package/src/models/MollieToken.ts +369 -0
- package/src/models/OneTimeToken.ts +131 -0
- package/src/models/Order.ts +1030 -0
- package/src/models/Organization.ts +1085 -0
- package/src/models/OrganizationRegistrationPeriod.ts +54 -0
- package/src/models/PasswordToken.ts +139 -0
- package/src/models/PayconiqPayment.ts +241 -0
- package/src/models/Payment.ts +216 -0
- package/src/models/Platform.ts +76 -0
- package/src/models/RegisterCode.ts +164 -0
- package/src/models/Registration.ts +405 -0
- package/src/models/RegistrationPeriod.ts +55 -0
- package/src/models/STCredit.ts +134 -0
- package/src/models/STInvoice.ts +507 -0
- package/src/models/STPackage.ts +324 -0
- package/src/models/STPendingInvoice.ts +308 -0
- package/src/models/StripeAccount.ts +71 -0
- package/src/models/StripeCheckoutSession.ts +22 -0
- package/src/models/StripePaymentIntent.ts +22 -0
- package/src/models/Ticket.ts +145 -0
- package/src/models/Token.test.ts +69 -0
- package/src/models/Token.ts +269 -0
- package/src/models/UsedRegisterCode.ts +166 -0
- package/src/models/User.ts +445 -0
- package/src/models/UserPermissions.ts +54 -0
- package/src/models/Webshop.ts +206 -0
- package/src/models/WebshopDiscountCode.ts +81 -0
- package/src/models/addresses/City.ts +31 -0
- package/src/models/addresses/PostalCode.test.ts +117 -0
- package/src/models/addresses/PostalCode.ts +164 -0
- package/src/models/addresses/Province.ts +20 -0
- package/src/models/addresses/Street.ts +25 -0
- package/src/models/index.ts +49 -0
- package/src/structures/OrganizationServerMetaData.ts +117 -0
|
@@ -0,0 +1,1085 @@
|
|
|
1
|
+
import { column, Database, Model } from "@simonbackx/simple-database";
|
|
2
|
+
import { DecodedRequest } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { I18n } from "@stamhoofd/backend-i18n";
|
|
5
|
+
import { Email, EmailInterfaceRecipient } from "@stamhoofd/email";
|
|
6
|
+
import { OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Address, Country, DNSRecordStatus, EmailTemplateType, Organization as OrganizationStruct, OrganizationEmail, OrganizationMetaData, OrganizationPrivateMetaData, OrganizationRecordsConfiguration, PaymentMethod, PaymentProvider, PrivatePaymentConfiguration, Recipient, Replacement, STPackageType, TransferSettings } from "@stamhoofd/structures";
|
|
7
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
8
|
+
import { AWSError } from 'aws-sdk';
|
|
9
|
+
import SES from 'aws-sdk/clients/sesv2';
|
|
10
|
+
import { PromiseResult } from 'aws-sdk/lib/request';
|
|
11
|
+
import { v4 as uuidv4 } from "uuid";
|
|
12
|
+
|
|
13
|
+
import { validateDNSRecords } from "../helpers/DNSValidator";
|
|
14
|
+
import { getEmailBuilder } from "../helpers/EmailBuilder";
|
|
15
|
+
import { OrganizationServerMetaData } from '../structures/OrganizationServerMetaData';
|
|
16
|
+
import { EmailTemplate, Group, OrganizationRegistrationPeriod, RegistrationPeriod, StripeAccount } from "./";
|
|
17
|
+
|
|
18
|
+
export class Organization extends Model {
|
|
19
|
+
static table = "organizations";
|
|
20
|
+
|
|
21
|
+
@column({
|
|
22
|
+
primary: true, type: "string", beforeSave(value) {
|
|
23
|
+
return value ?? uuidv4();
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
id!: string;
|
|
27
|
+
|
|
28
|
+
@column({ type: "string" })
|
|
29
|
+
name: string;
|
|
30
|
+
|
|
31
|
+
/// URL to a website page or a Facebook page (including http)
|
|
32
|
+
@column({ type: "string", nullable: true })
|
|
33
|
+
website: string | null = null;
|
|
34
|
+
|
|
35
|
+
/// A custom domain name that is used to host the register application (should be unique)
|
|
36
|
+
// E.g. inschrijven.scoutswetteren.be
|
|
37
|
+
@column({ type: "string", nullable: true })
|
|
38
|
+
registerDomain: string | null = null;
|
|
39
|
+
|
|
40
|
+
// Unique representation of this organization from a string, that is used to provide the default domains
|
|
41
|
+
// in uri.stamhoofd.be
|
|
42
|
+
@column({ type: "string" })
|
|
43
|
+
uri: string;
|
|
44
|
+
|
|
45
|
+
@column({ type: "string" })
|
|
46
|
+
periodId: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Public meta data
|
|
50
|
+
*/
|
|
51
|
+
@column({ type: "json", decoder: OrganizationMetaData })
|
|
52
|
+
meta: OrganizationMetaData;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Data only accessible by the owners / users with special permissions
|
|
56
|
+
*/
|
|
57
|
+
@column({ type: "json", decoder: OrganizationPrivateMetaData })
|
|
58
|
+
privateMeta: OrganizationPrivateMetaData = OrganizationPrivateMetaData.create({})
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Data only accessible by the server
|
|
62
|
+
*/
|
|
63
|
+
@column({ type: "json", decoder: OrganizationServerMetaData })
|
|
64
|
+
serverMeta: OrganizationServerMetaData = OrganizationServerMetaData.create({})
|
|
65
|
+
|
|
66
|
+
@column({ type: "json", decoder: Address })
|
|
67
|
+
address: Address;
|
|
68
|
+
|
|
69
|
+
@column({
|
|
70
|
+
type: "string", beforeSave: function (this: Organization) {
|
|
71
|
+
return this.name+"\n"+this.address.toString()
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
searchIndex: string
|
|
75
|
+
|
|
76
|
+
@column({
|
|
77
|
+
type: "datetime", beforeSave(old?: any) {
|
|
78
|
+
if (old !== undefined) {
|
|
79
|
+
return old;
|
|
80
|
+
}
|
|
81
|
+
const date = new Date()
|
|
82
|
+
date.setMilliseconds(0)
|
|
83
|
+
return date
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
createdAt: Date
|
|
87
|
+
|
|
88
|
+
@column({
|
|
89
|
+
type: "datetime", beforeSave() {
|
|
90
|
+
const date = new Date()
|
|
91
|
+
date.setMilliseconds(0)
|
|
92
|
+
return date
|
|
93
|
+
},
|
|
94
|
+
skipUpdate: true
|
|
95
|
+
})
|
|
96
|
+
updatedAt: Date
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Return default locale confiruation
|
|
100
|
+
*/
|
|
101
|
+
get i18n() {
|
|
102
|
+
return new I18n("nl", this.address.country)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Makes sure empty name is replaced with organization name
|
|
107
|
+
*/
|
|
108
|
+
get mappedTransferSettings(): TransferSettings {
|
|
109
|
+
return this.meta.transferSettings.fillMissing(TransferSettings.create({creditor: this.name}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// Methods
|
|
114
|
+
static async getByURI(uri: string): Promise<Organization | undefined> {
|
|
115
|
+
const [rows] = await Database.select(
|
|
116
|
+
`SELECT ${this.getDefaultSelect()} FROM ${this.table} WHERE \`uri\` = ? LIMIT 1`,
|
|
117
|
+
[uri]
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (rows.length == 0) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Read member + address from first row
|
|
125
|
+
return this.fromRow(rows[0][this.table]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Methods
|
|
129
|
+
static async getByEmail(email: string): Promise<Organization | undefined> {
|
|
130
|
+
if (["hallo@stamhoofd.be", "hallo@stamhoofd.nl"].includes(email)) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
if (email.startsWith('noreply-')) {
|
|
134
|
+
// Trim
|
|
135
|
+
email = email.substring("noreply-".length)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (email.endsWith("@stamhoofd.email")) {
|
|
139
|
+
const uri = email.substring(0, email.length - "@stamhoofd.email".length)
|
|
140
|
+
return await Organization.getByURI(uri)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (email.endsWith("@stamhoofd.be")) {
|
|
144
|
+
const uri = email.substring(0, email.length - "@stamhoofd.be".length)
|
|
145
|
+
return await Organization.getByURI(uri)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (email.endsWith("@stamhoofd.nl")) {
|
|
149
|
+
const uri = email.substring(0, email.length - "@stamhoofd.nl".length)
|
|
150
|
+
return await Organization.getByURI(uri)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const at = email.indexOf("@");
|
|
154
|
+
const domain = email.substring(at+1)
|
|
155
|
+
|
|
156
|
+
const [rows] = await Database.select(
|
|
157
|
+
`SELECT ${this.getDefaultSelect()} FROM ${this.table} WHERE privateMeta->"$.value.mailDomain" = ? LIMIT 1`,
|
|
158
|
+
[domain]
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (rows.length == 0) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Read member + address from first row
|
|
166
|
+
return this.fromRow(rows[0][this.table]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
// Methods
|
|
172
|
+
static async getByRegisterDomain(host: string): Promise<Organization | undefined> {
|
|
173
|
+
const [rows] = await Database.select(
|
|
174
|
+
`SELECT ${this.getDefaultSelect()} FROM ${this.table} WHERE \`registerDomain\` = ? LIMIT 1`,
|
|
175
|
+
[host]
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (rows.length == 0) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Read member + address from first row
|
|
183
|
+
return this.fromRow(rows[0][this.table]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get an Organization by looking at the host of a request
|
|
188
|
+
* Format is 2331c59a-0cbe-4279-871c-ea9d0474cd54.api.stamhoofd.app
|
|
189
|
+
* + switch country if needed
|
|
190
|
+
*/
|
|
191
|
+
static async getFromRequest(request: DecodedRequest<any, any, any>): Promise<Organization> {
|
|
192
|
+
const organization = await Organization.fromApiHost(request.host);
|
|
193
|
+
|
|
194
|
+
const i18n = I18n.fromRequest(request)
|
|
195
|
+
i18n.switchToLocale({ country: organization.address.country })
|
|
196
|
+
|
|
197
|
+
return organization
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get an Organization by looking at the host of a request
|
|
202
|
+
* Format is 2331c59a-0cbe-4279-871c-ea9d0474cd54.api.stamhoofd.app
|
|
203
|
+
*/
|
|
204
|
+
static async fromApiHost(host: string): Promise<Organization> {
|
|
205
|
+
const splitted = host.split('.')
|
|
206
|
+
if (splitted.length < 2) {
|
|
207
|
+
throw new SimpleError({
|
|
208
|
+
code: "invalid_host",
|
|
209
|
+
message: "Please specify the organization in the hostname",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const id = splitted[0]
|
|
213
|
+
const organization = await this.getByID(id);
|
|
214
|
+
if (!organization) {
|
|
215
|
+
throw new SimpleError({
|
|
216
|
+
code: "invalid_organization",
|
|
217
|
+
message: "No organization known for host " + host,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return organization;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Potentially includes a path
|
|
225
|
+
*/
|
|
226
|
+
getHost(i18n?: I18n): string {
|
|
227
|
+
if (this.registerDomain) {
|
|
228
|
+
let d = this.registerDomain;
|
|
229
|
+
|
|
230
|
+
if (i18n && i18n.language != this.i18n.language) {
|
|
231
|
+
d += "/"+i18n.language
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return d;
|
|
235
|
+
}
|
|
236
|
+
return this.getDefaultHost(i18n)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getDefaultHost(i18n?: I18n): string {
|
|
240
|
+
if (!STAMHOOFD.domains.registration) {
|
|
241
|
+
return STAMHOOFD.domains.dashboard + '/' + (i18n?.locale ?? this.i18n.locale) + '/leden/' + this.uri;
|
|
242
|
+
}
|
|
243
|
+
let defaultDomain = STAMHOOFD.domains.registration[this.address.country] ?? STAMHOOFD.domains.registration[""];
|
|
244
|
+
|
|
245
|
+
if (i18n && i18n.language != this.i18n.language) {
|
|
246
|
+
defaultDomain += "/"+i18n.language
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return this.uri + "." + defaultDomain;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
get marketingDomain(): string {
|
|
253
|
+
return STAMHOOFD.domains.marketing[this.address.country] ?? STAMHOOFD.domains.marketing[""];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getApiHost(): string {
|
|
257
|
+
const defaultDomain = STAMHOOFD.domains.api;
|
|
258
|
+
if (!defaultDomain) {
|
|
259
|
+
throw new Error("Missing hostname in environment")
|
|
260
|
+
}
|
|
261
|
+
return this.id+"." + defaultDomain;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async getStructure({emptyGroups} = {emptyGroups: false}): Promise<OrganizationStruct> {
|
|
265
|
+
const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: this.periodId }, {limit: 1})
|
|
266
|
+
const oPeriod = oPeriods[0];
|
|
267
|
+
const period = await RegistrationPeriod.getByID(this.periodId)
|
|
268
|
+
const groups = emptyGroups ? [] : (await (await import("./Group")).Group.getAll(this.id, this.periodId))
|
|
269
|
+
|
|
270
|
+
const struct = OrganizationStruct.create({
|
|
271
|
+
id: this.id,
|
|
272
|
+
name: this.name,
|
|
273
|
+
meta: this.meta,
|
|
274
|
+
address: this.address,
|
|
275
|
+
registerDomain: this.registerDomain,
|
|
276
|
+
uri: this.uri,
|
|
277
|
+
website: this.website,
|
|
278
|
+
groups: groups.map(g => g.getStructure()),
|
|
279
|
+
createdAt: this.createdAt,
|
|
280
|
+
period: OrganizationRegistrationPeriodStruct.create({
|
|
281
|
+
...oPeriod,
|
|
282
|
+
period: period!.getStructure()
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
if (this.meta.modules.disableActivities) {
|
|
287
|
+
// Only show groups that are in a given category
|
|
288
|
+
struct.groups = struct.categoryTree.categories[0]?.groups ?? []
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (emptyGroups) {
|
|
292
|
+
// Reduce data
|
|
293
|
+
struct.meta = struct.meta.clone()
|
|
294
|
+
struct.meta.categories = []
|
|
295
|
+
struct.meta.recordsConfiguration = OrganizationRecordsConfiguration.create({})
|
|
296
|
+
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return struct
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async cleanCategories(groups: {id: string}[]) {
|
|
303
|
+
const reachable = new Map<string, boolean>()
|
|
304
|
+
const queue = [this.meta.rootCategoryId]
|
|
305
|
+
reachable.set(this.meta.rootCategoryId, true)
|
|
306
|
+
let shouldSave = false;
|
|
307
|
+
|
|
308
|
+
const usedGroupIds = new Set<string>()
|
|
309
|
+
|
|
310
|
+
while (queue.length > 0) {
|
|
311
|
+
const id = queue.shift()
|
|
312
|
+
if (!id) {
|
|
313
|
+
break
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const category = this.meta.categories.find(c => c.id === id)
|
|
317
|
+
if (!category) {
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for (const i of category.categoryIds) {
|
|
322
|
+
if (!reachable.get(i)) {
|
|
323
|
+
reachable.set(i, true)
|
|
324
|
+
queue.push(i)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Remove groupIds that no longer exist or are in a different category already
|
|
329
|
+
let filtered = category.groupIds.filter(id => !!groups.find(g => g.id === id) && !usedGroupIds.has(id))
|
|
330
|
+
|
|
331
|
+
// Remove duplicate groups
|
|
332
|
+
filtered = Formatter.uniqueArray(filtered)
|
|
333
|
+
|
|
334
|
+
if (filtered.length !== category.groupIds.length) {
|
|
335
|
+
shouldSave = true;
|
|
336
|
+
console.log("Deleted "+ (category.groupIds.length - filtered.length) +" group ids from category " + category.id + ", in organization "+this.id)
|
|
337
|
+
category.groupIds = filtered
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (const groupId of category.groupIds) {
|
|
341
|
+
usedGroupIds.add(groupId)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const reachableCategoryIds = [...reachable.keys()]
|
|
346
|
+
|
|
347
|
+
// Delete all categories that are not reachable anymore
|
|
348
|
+
const beforeCount = this.meta.categories.length;
|
|
349
|
+
this.meta.categories = this.meta.categories.filter(c => reachableCategoryIds.includes(c.id))
|
|
350
|
+
|
|
351
|
+
if (this.meta.categories.length !== beforeCount) {
|
|
352
|
+
console.log("Deleted "+ (beforeCount - this.meta.categories.length) +" categories from organization "+this.id)
|
|
353
|
+
await this.save()
|
|
354
|
+
} else {
|
|
355
|
+
if (shouldSave) {
|
|
356
|
+
await this.save()
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async updateDNSRecords() {
|
|
362
|
+
const organization = this;
|
|
363
|
+
|
|
364
|
+
// Check initial status
|
|
365
|
+
let isValidRecords = true
|
|
366
|
+
for (const record of organization.privateMeta.dnsRecords) {
|
|
367
|
+
if (record.status != DNSRecordStatus.Valid) {
|
|
368
|
+
isValidRecords = false
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const { allValid } = await validateDNSRecords(organization.privateMeta.dnsRecords)
|
|
373
|
+
|
|
374
|
+
if (organization.registerDomain ?? organization.privateMeta.pendingRegisterDomain) {
|
|
375
|
+
const registerDomainRecord = (organization.privateMeta.pendingRegisterDomain ?? organization.registerDomain)+"."
|
|
376
|
+
const records = organization.privateMeta.dnsRecords.filter(r => r.name === registerDomainRecord)
|
|
377
|
+
const areRegisterDomainRecordsValid = records.length === 0 || records.every(r => r.status === DNSRecordStatus.Valid)
|
|
378
|
+
|
|
379
|
+
if (areRegisterDomainRecordsValid) {
|
|
380
|
+
// We can setup the register domain if needed
|
|
381
|
+
if (organization.privateMeta.pendingRegisterDomain !== null) {
|
|
382
|
+
organization.registerDomain = organization.privateMeta.pendingRegisterDomain
|
|
383
|
+
organization.privateMeta.pendingRegisterDomain = null;
|
|
384
|
+
|
|
385
|
+
console.log("Did set register domain for "+this.id+" to "+organization.registerDomain)
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
// Clear register domain
|
|
389
|
+
if (organization.registerDomain) {
|
|
390
|
+
// We need to clear it, to prevent sending e-mails with invalid links
|
|
391
|
+
organization.privateMeta.pendingRegisterDomain = organization.privateMeta.pendingRegisterDomain ?? organization.registerDomain
|
|
392
|
+
organization.registerDomain = null
|
|
393
|
+
|
|
394
|
+
console.log("Cleared register domain for "+this.id+" because of invalid non txt records")
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (allValid) {
|
|
400
|
+
if (organization.privateMeta.pendingMailDomain !== null) {
|
|
401
|
+
organization.privateMeta.mailDomain = organization.privateMeta.pendingMailDomain
|
|
402
|
+
organization.privateMeta.pendingMailDomain = null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const wasUnstable = organization.serverMeta.isDNSUnstable
|
|
406
|
+
organization.serverMeta.markDNSValid()
|
|
407
|
+
|
|
408
|
+
const didSendDomainSetupMail = organization.serverMeta.didSendDomainSetupMail
|
|
409
|
+
const didSendWarning = organization.serverMeta.DNSRecordWarningCount > 0
|
|
410
|
+
organization.serverMeta.DNSRecordWarningCount = 0
|
|
411
|
+
|
|
412
|
+
const wasActive = this.privateMeta.mailDomainActive
|
|
413
|
+
await this.updateAWSMailIdenitity()
|
|
414
|
+
|
|
415
|
+
// yay! Do not Save until after doing AWS changes
|
|
416
|
+
await organization.save()
|
|
417
|
+
|
|
418
|
+
if (wasUnstable && !organization.serverMeta.isDNSUnstable) {
|
|
419
|
+
console.warn('DNS settings became stable for ' + this.name + ' '+this.id)
|
|
420
|
+
|
|
421
|
+
await this.sendEmailTemplate({
|
|
422
|
+
type: EmailTemplateType.OrganizationStableDNS,
|
|
423
|
+
bcc: true
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
} else if (!wasActive && this.privateMeta.mailDomainActive && (!didSendDomainSetupMail || didSendWarning) && !organization.serverMeta.isDNSUnstable) {
|
|
427
|
+
organization.serverMeta.didSendDomainSetupMail = true
|
|
428
|
+
await organization.save()
|
|
429
|
+
|
|
430
|
+
if (!didSendDomainSetupMail) {
|
|
431
|
+
await this.sendEmailTemplate({
|
|
432
|
+
type: EmailTemplateType.OrganizationDNSSetupComplete
|
|
433
|
+
})
|
|
434
|
+
} else {
|
|
435
|
+
await this.sendEmailTemplate({
|
|
436
|
+
type: EmailTemplateType.OrganizationValidDNS
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
// DNS settings gone broken
|
|
442
|
+
if (organization.privateMeta.mailDomain) {
|
|
443
|
+
organization.privateMeta.pendingMailDomain = organization.privateMeta.pendingMailDomain ?? organization.privateMeta.mailDomain
|
|
444
|
+
organization.privateMeta.mailDomain = null
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const wasDNSUnstable = organization.serverMeta.isDNSUnstable
|
|
448
|
+
|
|
449
|
+
organization.serverMeta.markDNSFailure()
|
|
450
|
+
|
|
451
|
+
// disable AWS emails
|
|
452
|
+
this.privateMeta.mailDomainActive = false
|
|
453
|
+
|
|
454
|
+
// save
|
|
455
|
+
await organization.save()
|
|
456
|
+
|
|
457
|
+
if (!wasDNSUnstable && organization.serverMeta.isDNSUnstable) {
|
|
458
|
+
// DNS became instable
|
|
459
|
+
console.warn('DNS settings became instable for ' + this.name + ' '+this.id)
|
|
460
|
+
|
|
461
|
+
await this.sendEmailTemplate({
|
|
462
|
+
type: EmailTemplateType.OrganizationUnstableDNS,
|
|
463
|
+
bcc: true
|
|
464
|
+
})
|
|
465
|
+
} else if (!organization.serverMeta.isDNSUnstable && organization.serverMeta.didSendDomainSetupMail && organization.serverMeta.DNSRecordWarningCount == 0) {
|
|
466
|
+
organization.serverMeta.DNSRecordWarningCount += 1
|
|
467
|
+
await organization.save()
|
|
468
|
+
|
|
469
|
+
await this.sendEmailTemplate({
|
|
470
|
+
type: EmailTemplateType.OrganizationInvalidDNS
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async sendEmailTemplate(data: {
|
|
477
|
+
type: EmailTemplateType,
|
|
478
|
+
personal?: boolean,
|
|
479
|
+
replyTo?: string,
|
|
480
|
+
bcc?: boolean
|
|
481
|
+
}) {
|
|
482
|
+
// First fetch template
|
|
483
|
+
const templates = (await EmailTemplate.where({ type: data.type, organizationId: null }))
|
|
484
|
+
|
|
485
|
+
if (templates.length == 0) {
|
|
486
|
+
console.error("Could not find email template for type "+data.type)
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const template = templates[0]
|
|
491
|
+
|
|
492
|
+
const recipients = await this.getAdminRecipients();
|
|
493
|
+
|
|
494
|
+
const defaultI18n = new I18n("nl", Country.Belgium)
|
|
495
|
+
const i18n = this.i18n;
|
|
496
|
+
|
|
497
|
+
const replacementStrings = [
|
|
498
|
+
{
|
|
499
|
+
from: defaultI18n.$t("shared.domains.marketing"),
|
|
500
|
+
to: i18n.$t("shared.domains.marketing")
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
from: defaultI18n.$t("shared.emails.general"),
|
|
504
|
+
to: i18n.$t("shared.emails.general")
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
from: defaultI18n.$t("shared.domains.email"),
|
|
508
|
+
to: i18n.$t("shared.domains.email")
|
|
509
|
+
}
|
|
510
|
+
];
|
|
511
|
+
|
|
512
|
+
let html = template.html;
|
|
513
|
+
|
|
514
|
+
for (const s of replacementStrings) {
|
|
515
|
+
html = html.replaceAll(s.from, s.to)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Create e-mail builder
|
|
519
|
+
const builder = await getEmailBuilder(this, {
|
|
520
|
+
recipients,
|
|
521
|
+
subject: template.subject,
|
|
522
|
+
html,
|
|
523
|
+
from: data.personal ? Email.getPersonalEmailFor(this.i18n) : Email.getInternalEmailFor(this.i18n),
|
|
524
|
+
singleBcc: data.bcc ? 'simon@stamhoofd.be' : undefined,
|
|
525
|
+
replyTo: data.replyTo,
|
|
526
|
+
type: 'transactional',
|
|
527
|
+
defaultReplacements: [
|
|
528
|
+
Replacement.create({
|
|
529
|
+
token: 'mailDomain',
|
|
530
|
+
value: this.privateMeta.mailDomain ?? this.privateMeta.pendingMailDomain ?? ''
|
|
531
|
+
})
|
|
532
|
+
],
|
|
533
|
+
unsubscribeType: 'marketing',
|
|
534
|
+
fromStamhoofd: true
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
Email.schedule(builder)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async deleteAWSMailIdenitity(mailDomain: string) {
|
|
541
|
+
|
|
542
|
+
// Protect specific domain names
|
|
543
|
+
if (["stamhoofd.be", "stamhoofd.nl", "stamhoofd.shop", "stamhoofd.app", "stamhoofd.email"].includes(mailDomain)) {
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (STAMHOOFD.environment != "production") {
|
|
548
|
+
// Temporary ignore this
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const sesv2 = new SES();
|
|
553
|
+
|
|
554
|
+
// Check if mail identitiy already exists..
|
|
555
|
+
let exists = false
|
|
556
|
+
let existing: PromiseResult<SES.GetEmailIdentityResponse, AWSError> | undefined = undefined
|
|
557
|
+
try {
|
|
558
|
+
existing = await sesv2.getEmailIdentity({
|
|
559
|
+
EmailIdentity: mailDomain
|
|
560
|
+
}).promise()
|
|
561
|
+
exists = true
|
|
562
|
+
|
|
563
|
+
// Check if DKIM keys are the same
|
|
564
|
+
if (existing.VerifiedForSendingStatus === true) {
|
|
565
|
+
console.log("Cant delete AWS mail idenitiy @"+this.id+" for "+mailDomain+": already validated and might be in use by other organizations")
|
|
566
|
+
return
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
console.log("Deleting AWS mail idenitiy @"+this.id+" for "+mailDomain)
|
|
570
|
+
|
|
571
|
+
await sesv2.deleteEmailIdentity({
|
|
572
|
+
EmailIdentity: mailDomain
|
|
573
|
+
}).promise()
|
|
574
|
+
console.log("Deleted AWS mail idenitiy @"+this.id+" for "+this.privateMeta.mailDomain)
|
|
575
|
+
|
|
576
|
+
} catch (e) {
|
|
577
|
+
console.error("Could not delete AWS email identitiy @"+this.id+" for "+this.privateMeta.mailDomain)
|
|
578
|
+
console.error(e)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Create or update the AWS mail idenitiy and also update the active state of the mailDomain
|
|
584
|
+
*/
|
|
585
|
+
async updateAWSMailIdenitity() {
|
|
586
|
+
if (this.privateMeta.mailDomain === null) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Protect specific domain names
|
|
591
|
+
if (["stamhoofd.be", "stamhoofd.nl", "stamhoofd.shop", "stamhoofd.app", "stamhoofd.email"].includes(this.privateMeta.mailDomain)) {
|
|
592
|
+
console.error("Tried to validate AWS mail identity with protected domains @"+this.id)
|
|
593
|
+
this.privateMeta.mailDomainActive = false;
|
|
594
|
+
return
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (STAMHOOFD.environment != "production") {
|
|
598
|
+
// Temporary ignore this
|
|
599
|
+
this.privateMeta.mailDomainActive = true;
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const sesv2 = new SES();
|
|
604
|
+
|
|
605
|
+
// Check if mail identitiy already exists..
|
|
606
|
+
let exists = false
|
|
607
|
+
let existing: PromiseResult<SES.GetEmailIdentityResponse, AWSError> | undefined = undefined
|
|
608
|
+
try {
|
|
609
|
+
existing = await sesv2.getEmailIdentity({
|
|
610
|
+
EmailIdentity: this.privateMeta.mailDomain
|
|
611
|
+
}).promise()
|
|
612
|
+
exists = true
|
|
613
|
+
|
|
614
|
+
console.log("AWS mail idenitiy exists already: just checking the verification status in AWS @"+this.id)
|
|
615
|
+
|
|
616
|
+
if (existing.ConfigurationSetName !== "stamhoofd-domains") {
|
|
617
|
+
// Not allowed to use this identity
|
|
618
|
+
this.privateMeta.mailDomainActive = false;
|
|
619
|
+
console.error("Organization is not allowed to use email identity "+this.privateMeta.mailDomain+" @"+this.id+", got "+existing.ConfigurationSetName)
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.privateMeta.mailDomainActive = existing.VerifiedForSendingStatus ?? false
|
|
624
|
+
|
|
625
|
+
if (existing.VerifiedForSendingStatus !== true) {
|
|
626
|
+
console.error("Not validated @"+this.id)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (existing.VerifiedForSendingStatus !== true && existing.DkimAttributes?.Status === "FAILED") {
|
|
630
|
+
console.error("AWS failed to verify DKIM records. Triggering a forced recheck @"+this.id)
|
|
631
|
+
await sesv2.deleteEmailIdentity({
|
|
632
|
+
EmailIdentity: this.privateMeta.mailDomain
|
|
633
|
+
}).promise()
|
|
634
|
+
|
|
635
|
+
// Recreate it immediately
|
|
636
|
+
exists = false
|
|
637
|
+
}
|
|
638
|
+
} catch (e) {
|
|
639
|
+
console.error(e)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (!exists) {
|
|
643
|
+
console.log("Creating email identity in AWS SES...")
|
|
644
|
+
|
|
645
|
+
const result = await sesv2.createEmailIdentity({
|
|
646
|
+
EmailIdentity: this.privateMeta.mailDomain,
|
|
647
|
+
ConfigurationSetName: "stamhoofd-domains",
|
|
648
|
+
DkimSigningAttributes: {
|
|
649
|
+
DomainSigningPrivateKey: this.serverMeta.privateDKIMKey!,
|
|
650
|
+
DomainSigningSelector: "stamhoofd"
|
|
651
|
+
},
|
|
652
|
+
Tags: [
|
|
653
|
+
{
|
|
654
|
+
"Key": "OrganizationId",
|
|
655
|
+
"Value": this.id
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"Key": "Environment",
|
|
659
|
+
"Value": STAMHOOFD.environment ?? "Unknown"
|
|
660
|
+
}
|
|
661
|
+
]
|
|
662
|
+
|
|
663
|
+
}).promise()
|
|
664
|
+
this.privateMeta.mailDomainActive = result.VerifiedForSendingStatus ?? false
|
|
665
|
+
|
|
666
|
+
// Disable email forwarding of bounces and complaints
|
|
667
|
+
// We handle this now with the configuration set
|
|
668
|
+
await sesv2.putEmailIdentityFeedbackAttributes({
|
|
669
|
+
EmailIdentity: this.privateMeta.mailDomain,
|
|
670
|
+
EmailForwardingEnabled: false
|
|
671
|
+
}).promise()
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (this.privateMeta.mailFromDomain && (!exists || (existing && (!existing.MailFromAttributes || existing.MailFromAttributes.MailFromDomain !== this.privateMeta.mailFromDomain)))) {
|
|
675
|
+
// Also set a from domain, to fix SPF
|
|
676
|
+
console.log("Setting mail from domain: "+this.privateMeta.mailFromDomain+" for "+this.id)
|
|
677
|
+
const params = {
|
|
678
|
+
EmailIdentity: this.privateMeta.mailDomain,
|
|
679
|
+
BehaviorOnMxFailure: "USE_DEFAULT_VALUE",
|
|
680
|
+
MailFromDomain: this.privateMeta.mailFromDomain,
|
|
681
|
+
};
|
|
682
|
+
await sesv2.putEmailIdentityMailFromAttributes(params).promise();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async checkDrips() {
|
|
687
|
+
const days7 = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
|
|
688
|
+
|
|
689
|
+
// Welcome drip
|
|
690
|
+
// Created maximum 7 days ago
|
|
691
|
+
if (this.createdAt > days7 && !this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripWelcome)) {
|
|
692
|
+
await this.sendEmailTemplate({
|
|
693
|
+
type: EmailTemplateType.OrganizationDripWelcome,
|
|
694
|
+
personal: true
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripWelcome);
|
|
698
|
+
await this.save();
|
|
699
|
+
|
|
700
|
+
return; // Never send more than 1 drip email on the same day
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Webshop trial checkin
|
|
704
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripWebshopTrialCheckin)) {
|
|
705
|
+
if (this.meta.packages.isWebshopsTrial) {
|
|
706
|
+
const activeTime = this.meta.packages.getActiveTime(STPackageType.TrialWebshops)
|
|
707
|
+
if (activeTime !== null && activeTime > 4 * 24 * 60 * 60 * 1000) {
|
|
708
|
+
// 7 days checkin
|
|
709
|
+
await this.sendEmailTemplate({
|
|
710
|
+
type: EmailTemplateType.OrganizationDripWebshopTrialCheckin,
|
|
711
|
+
personal: true
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripWebshopTrialCheckin);
|
|
715
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripMembersTrialCheckin); // also mark members checkin
|
|
716
|
+
await this.save();
|
|
717
|
+
|
|
718
|
+
return; // Never send more than 1 drip email on the same day
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Members trial checkin
|
|
724
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripMembersTrialCheckin)) {
|
|
725
|
+
if (this.meta.packages.isMembersTrial) {
|
|
726
|
+
const activeTime = this.meta.packages.getActiveTime(STPackageType.TrialMembers)
|
|
727
|
+
if (activeTime !== null && activeTime > 4 * 24 * 60 * 60 * 1000) {
|
|
728
|
+
// 7 days checkin
|
|
729
|
+
await this.sendEmailTemplate({
|
|
730
|
+
type: EmailTemplateType.OrganizationDripMembersTrialCheckin,
|
|
731
|
+
personal: true
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripMembersTrialCheckin);
|
|
735
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripWebshopTrialCheckin); // Also mark webshop trial checkin
|
|
736
|
+
await this.save();
|
|
737
|
+
|
|
738
|
+
return; // Never send more than 1 drip email on the same day
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Webshop trial expired after 1 week
|
|
744
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripWebshopTrialExpired)) {
|
|
745
|
+
if (!this.meta.packages.useWebshops) {
|
|
746
|
+
const deactivatedTime = this.meta.packages.getDeactivatedTime(STPackageType.TrialWebshops)
|
|
747
|
+
if (deactivatedTime !== null && deactivatedTime < 14 * 24 * 60 * 60 * 1000 && deactivatedTime > 7 * 24 * 60 * 60 * 1000) {
|
|
748
|
+
await this.sendEmailTemplate({
|
|
749
|
+
type: EmailTemplateType.OrganizationDripWebshopTrialExpired,
|
|
750
|
+
personal: true
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripWebshopTrialExpired);
|
|
754
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripMembersTrialExpired); // also mark members
|
|
755
|
+
await this.save();
|
|
756
|
+
|
|
757
|
+
return; // Never send more than 1 drip email on the same day
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripMembersTrialExpired)) {
|
|
763
|
+
if (!this.meta.packages.useMembers) {
|
|
764
|
+
const deactivatedTime = this.meta.packages.getDeactivatedTime(STPackageType.TrialMembers)
|
|
765
|
+
if (deactivatedTime !== null && deactivatedTime < 14 * 24 * 60 * 60 * 1000 && deactivatedTime > 7 * 24 * 60 * 60 * 1000) {
|
|
766
|
+
await this.sendEmailTemplate({
|
|
767
|
+
type: EmailTemplateType.OrganizationDripMembersTrialExpired,
|
|
768
|
+
personal: true
|
|
769
|
+
})
|
|
770
|
+
|
|
771
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripMembersTrialExpired);
|
|
772
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripWebshopTrialExpired); // also mark webshops
|
|
773
|
+
await this.save();
|
|
774
|
+
|
|
775
|
+
return; // Never send more than 1 drip email on the same day
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// trial expired reminder (after 10 months)
|
|
781
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripTrialExpiredReminder)) {
|
|
782
|
+
if (!this.meta.packages.isPaid && !this.meta.packages.wasPaid) {
|
|
783
|
+
const deactivatedTime1 = this.meta.packages.getDeactivatedTime(STPackageType.TrialWebshops)
|
|
784
|
+
const deactivatedTime2 = this.meta.packages.getDeactivatedTime(STPackageType.TrialMembers)
|
|
785
|
+
|
|
786
|
+
const deactivatedTime = deactivatedTime1 && deactivatedTime2 ? Math.max(deactivatedTime1, deactivatedTime2) : (deactivatedTime1 ? deactivatedTime1 : deactivatedTime2)
|
|
787
|
+
|
|
788
|
+
if (deactivatedTime !== null && deactivatedTime > 10 * 30 * 24 * 60 * 60 * 1000 && deactivatedTime < 13 * 31 * 24 * 60 * 60 * 1000) {
|
|
789
|
+
await this.sendEmailTemplate({
|
|
790
|
+
type: EmailTemplateType.OrganizationDripTrialExpiredReminder,
|
|
791
|
+
personal: true,
|
|
792
|
+
bcc: true
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripTrialExpiredReminder);
|
|
796
|
+
await this.save();
|
|
797
|
+
|
|
798
|
+
return; // Never send more than 1 drip email on the same day
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripWebshopNotRenewed)) {
|
|
804
|
+
if (!this.meta.packages.useWebshops) {
|
|
805
|
+
const deactivatedTime = this.meta.packages.getDeactivatedTime(STPackageType.Webshops)
|
|
806
|
+
|
|
807
|
+
if (deactivatedTime !== null && deactivatedTime > 30 * 24 * 60 * 60 * 1000 && deactivatedTime < 30*3 * 24 * 60 * 60 * 1000) {
|
|
808
|
+
await this.sendEmailTemplate({
|
|
809
|
+
type: EmailTemplateType.OrganizationDripWebshopNotRenewed,
|
|
810
|
+
personal: true,
|
|
811
|
+
bcc: true
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripWebshopNotRenewed);
|
|
815
|
+
await this.save();
|
|
816
|
+
|
|
817
|
+
return; // Never send more than 1 drip email on the same day
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!this.serverMeta.hasEmail(EmailTemplateType.OrganizationDripMembersNotRenewed)) {
|
|
823
|
+
if (!this.meta.packages.useMembers) {
|
|
824
|
+
const deactivatedTime = this.meta.packages.getDeactivatedTime(STPackageType.Members)
|
|
825
|
+
|
|
826
|
+
if (deactivatedTime !== null && deactivatedTime > 30 * 24 * 60 * 60 * 1000 && deactivatedTime < 30*3 * 24 * 60 * 60 * 1000) {
|
|
827
|
+
await this.sendEmailTemplate({
|
|
828
|
+
type: EmailTemplateType.OrganizationDripMembersNotRenewed,
|
|
829
|
+
personal: true,
|
|
830
|
+
bcc: true
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
this.serverMeta.addEmail(EmailTemplateType.OrganizationDripMembersNotRenewed);
|
|
834
|
+
await this.save();
|
|
835
|
+
|
|
836
|
+
return; // Never send more than 1 drip email on the same day
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* E-mail address when we receive replies for organization@stamhoofd.email.
|
|
844
|
+
* Note that this sould be private because it can contain personal e-mail addresses if the organization is not configured correctly
|
|
845
|
+
*/
|
|
846
|
+
async getReplyEmails(): Promise<EmailInterfaceRecipient[]> {
|
|
847
|
+
const sender: OrganizationEmail | undefined = this.privateMeta.emails.find(e => e.default) ?? this.privateMeta.emails[0];
|
|
848
|
+
|
|
849
|
+
if (sender) {
|
|
850
|
+
return [
|
|
851
|
+
{
|
|
852
|
+
name: sender.name,
|
|
853
|
+
email: sender.email
|
|
854
|
+
}
|
|
855
|
+
]
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return await this.getAdminToEmails()
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* These email addresess are private
|
|
863
|
+
*/
|
|
864
|
+
async getFullAdmins() {
|
|
865
|
+
// Circular reference fix
|
|
866
|
+
const User = (await import('./User')).User;
|
|
867
|
+
const admins = await User.getAdmins([this.id], {verified: true})
|
|
868
|
+
|
|
869
|
+
// Only full access
|
|
870
|
+
return admins.filter(a => a.permissions && a.permissions.forOrganization(this)?.hasFullAccess())
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* These email addresess are private
|
|
875
|
+
*/
|
|
876
|
+
async getAdminToEmails(): Promise<EmailInterfaceRecipient[]> {
|
|
877
|
+
const filtered = await this.getFullAdmins()
|
|
878
|
+
|
|
879
|
+
if (STAMHOOFD.environment === "production") {
|
|
880
|
+
if (filtered.length > 1) {
|
|
881
|
+
// remove stamhoofd email addresses
|
|
882
|
+
const f = filtered.flatMap(f => f.getEmailTo() ).filter(e => !e.email.endsWith("@stamhoofd.be") && !e.email.endsWith("@stamhoofd.nl"))
|
|
883
|
+
if (f.length > 0) {
|
|
884
|
+
return f
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return filtered.flatMap(f => f.getEmailTo() )
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* These email addresess are private
|
|
894
|
+
*/
|
|
895
|
+
async getAdminRecipients(): Promise<Recipient[]> {
|
|
896
|
+
let filtered = await this.getFullAdmins()
|
|
897
|
+
|
|
898
|
+
if (STAMHOOFD.environment === "production") {
|
|
899
|
+
if (filtered.length > 1) {
|
|
900
|
+
// remove stamhoofd email addresses
|
|
901
|
+
filtered = filtered.filter(e => !e.email.endsWith("@stamhoofd.be") && !e.email.endsWith("@stamhoofd.nl"))
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return filtered.flatMap(f => {
|
|
906
|
+
return Recipient.create({
|
|
907
|
+
firstName: f.firstName,
|
|
908
|
+
lastName: f.lastName,
|
|
909
|
+
email: f.email,
|
|
910
|
+
replacements: [
|
|
911
|
+
Replacement.create({
|
|
912
|
+
token: "firstName",
|
|
913
|
+
value: f.firstName ?? ""
|
|
914
|
+
}),
|
|
915
|
+
Replacement.create({
|
|
916
|
+
token: "lastName",
|
|
917
|
+
value: f.lastName ?? ""
|
|
918
|
+
}),
|
|
919
|
+
Replacement.create({
|
|
920
|
+
token: "email",
|
|
921
|
+
value: f.email
|
|
922
|
+
}),
|
|
923
|
+
Replacement.create({
|
|
924
|
+
token: "organizationName",
|
|
925
|
+
value: this.name
|
|
926
|
+
})
|
|
927
|
+
]
|
|
928
|
+
})
|
|
929
|
+
} )
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* These email addresess are private
|
|
934
|
+
*/
|
|
935
|
+
async getInvoicingToEmails() {
|
|
936
|
+
// Circular reference fix
|
|
937
|
+
const User = (await import('./User')).User;
|
|
938
|
+
const admins = await User.where({ organizationId: this.id, permissions: { sign: "!=", value: null }})
|
|
939
|
+
const filtered = admins.filter(a => a.organizationPermissions && (a.organizationPermissions.hasFullAccess(this.privateMeta.roles) || a.organizationPermissions.hasFinanceAccess(this.privateMeta.roles)))
|
|
940
|
+
|
|
941
|
+
if (filtered.length > 0) {
|
|
942
|
+
return filtered.map(f => f.getEmailTo() ).join(", ")
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return undefined
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Return default e-mail address for important e-mails that should have the highest deliverability
|
|
950
|
+
*/
|
|
951
|
+
getStrongEmail(i18n: I18n, withName = true) {
|
|
952
|
+
if (!withName) {
|
|
953
|
+
return ('noreply-' + this.uri+"@"+i18n.$t("shared.domains.email"));
|
|
954
|
+
}
|
|
955
|
+
return '"'+this.name.replaceAll("\"", "\\\"")+'" <'+ ('noreply-' + this.uri+"@"+i18n.$t("shared.domains.email")) +'>'
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
getEmail(id: string | null, strongDefault = false): { from: string; replyTo: string | undefined } {
|
|
959
|
+
if (id === null) {
|
|
960
|
+
return this.getDefaultEmail(strongDefault)
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Send confirmation e-mail
|
|
964
|
+
let from = strongDefault ? this.getStrongEmail(this.i18n, false) : this.uri+"@stamhoofd.email";
|
|
965
|
+
const sender: OrganizationEmail | undefined = this.privateMeta.emails.find(e => e.id === id)
|
|
966
|
+
let replyTo: string | undefined = undefined
|
|
967
|
+
|
|
968
|
+
if (sender) {
|
|
969
|
+
replyTo = sender.email
|
|
970
|
+
|
|
971
|
+
// Can we send from this e-mail or reply-to?
|
|
972
|
+
if (replyTo && this.privateMeta.mailDomain && this.privateMeta.mailDomainActive && sender.email.endsWith("@"+this.privateMeta.mailDomain)) {
|
|
973
|
+
from = sender.email
|
|
974
|
+
replyTo = undefined
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Include name in form field
|
|
978
|
+
if (sender.name) {
|
|
979
|
+
from = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
|
|
980
|
+
} else {
|
|
981
|
+
from = '"'+this.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (replyTo) {
|
|
985
|
+
if (sender.name) {
|
|
986
|
+
replyTo = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
|
|
987
|
+
} else {
|
|
988
|
+
replyTo = '"'+this.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return { from, replyTo }
|
|
992
|
+
}
|
|
993
|
+
return this.getDefaultEmail(strongDefault)
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
getGroupEmail(group: Group) {
|
|
997
|
+
return this.getEmail(group.privateSettings.defaultEmailId)
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
getDefaultEmail(strongDefault = false): { from: string; replyTo: string | undefined } {
|
|
1001
|
+
// Send confirmation e-mail
|
|
1002
|
+
let from = strongDefault ? this.getStrongEmail(this.i18n, false) : this.uri+"@stamhoofd.email";
|
|
1003
|
+
const sender: OrganizationEmail | undefined = this.privateMeta.emails.find(e => e.default) ?? this.privateMeta.emails[0];
|
|
1004
|
+
let replyTo: string | undefined = undefined
|
|
1005
|
+
|
|
1006
|
+
if (sender) {
|
|
1007
|
+
replyTo = sender.email
|
|
1008
|
+
|
|
1009
|
+
// Can we send from this e-mail or reply-to?
|
|
1010
|
+
if (replyTo && this.privateMeta.mailDomain && this.privateMeta.mailDomainActive && sender.email.endsWith("@"+this.privateMeta.mailDomain)) {
|
|
1011
|
+
from = sender.email
|
|
1012
|
+
replyTo = undefined
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Include name in form field
|
|
1016
|
+
if (sender.name) {
|
|
1017
|
+
from = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
|
|
1018
|
+
} else {
|
|
1019
|
+
from = '"'+this.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (replyTo) {
|
|
1023
|
+
if (sender.name) {
|
|
1024
|
+
replyTo = '"'+sender.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
|
|
1025
|
+
} else {
|
|
1026
|
+
replyTo = '"'+this.name.replaceAll("\"", "\\\"")+"\" <"+replyTo+">"
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
} else {
|
|
1030
|
+
from = '"'+this.name.replaceAll("\"", "\\\"")+"\" <"+from+">"
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
from, replyTo
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
async getPaymentProviderFor(method: PaymentMethod, config: PrivatePaymentConfiguration): Promise<{
|
|
1039
|
+
provider: PaymentProvider | null,
|
|
1040
|
+
stripeAccount: StripeAccount | null
|
|
1041
|
+
}> {
|
|
1042
|
+
let stripeAccount = (config.stripeAccountId ? (await StripeAccount.getByID(config.stripeAccountId)) : null) ?? null
|
|
1043
|
+
if (stripeAccount && stripeAccount.organizationId !== this.id) {
|
|
1044
|
+
console.warn('Stripe account '+stripeAccount.id+' is not linked to organization '+this.id);
|
|
1045
|
+
stripeAccount = null
|
|
1046
|
+
}
|
|
1047
|
+
const provider = this.privateMeta.getPaymentProviderFor(method, stripeAccount?.meta)
|
|
1048
|
+
if (provider === null && ![PaymentMethod.Unknown, PaymentMethod.Transfer, PaymentMethod.PointOfSale].includes(method)) {
|
|
1049
|
+
if (!stripeAccount && config.stripeAccountId) {
|
|
1050
|
+
console.warn('Missing stripe account id ' + config.stripeAccountId);
|
|
1051
|
+
}
|
|
1052
|
+
throw new SimpleError({
|
|
1053
|
+
code: 'payment_provider_not_configured',
|
|
1054
|
+
message: 'Payment provider not configured for '+method,
|
|
1055
|
+
human: 'Deze betaalmethode werd helaas niet volledig geconfigureerd. Probeer later even opnieuw, neem contact met ons op of kies een andere betaalmethode.'
|
|
1056
|
+
})
|
|
1057
|
+
}
|
|
1058
|
+
return {
|
|
1059
|
+
provider,
|
|
1060
|
+
stripeAccount
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
async getConnectedPaymentProviders(): Promise<PaymentProvider[]> {
|
|
1065
|
+
const allPaymentMethods = Object.values(PaymentMethod)
|
|
1066
|
+
const providers: PaymentProvider[] = []
|
|
1067
|
+
|
|
1068
|
+
let stripeAccounts: (StripeAccount|null)[] = await StripeAccount.where({ organizationId: this.id, status: 'active' })
|
|
1069
|
+
|
|
1070
|
+
if (stripeAccounts.length === 0) {
|
|
1071
|
+
stripeAccounts = [null]
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
for (const account of stripeAccounts) {
|
|
1075
|
+
for (const method of allPaymentMethods) {
|
|
1076
|
+
const provider = this.privateMeta.getPaymentProviderFor(method, account?.meta)
|
|
1077
|
+
if (provider && !providers.includes(provider)) {
|
|
1078
|
+
providers.push(provider)
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return providers
|
|
1084
|
+
}
|
|
1085
|
+
}
|