@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,352 @@
|
|
|
1
|
+
import { column, Model } from "@simonbackx/simple-database";
|
|
2
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
3
|
+
import { I18n } from "@stamhoofd/backend-i18n";
|
|
4
|
+
import { Email } from "@stamhoofd/email";
|
|
5
|
+
import basex from "base-x";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
import { v4 as uuidv4 } from "uuid";
|
|
8
|
+
|
|
9
|
+
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
10
|
+
const bs58 = basex(ALPHABET)
|
|
11
|
+
|
|
12
|
+
async function randomBytes(size: number): Promise<Buffer> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
crypto.randomBytes(size, (err: Error | null, buf: Buffer) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
reject(err);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
resolve(buf);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function randomInt(max: number): Promise<number> {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
crypto.randomInt(max, (err: Error | null, n: number) => {
|
|
27
|
+
if (err) {
|
|
28
|
+
reject(err);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
resolve(n)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Holds the verificationCodes for a given email (not a user, since a user can switch email addresses and might avoid verification that way)
|
|
39
|
+
*/
|
|
40
|
+
export class EmailVerificationCode extends Model {
|
|
41
|
+
static table = "email_verification_codes";
|
|
42
|
+
|
|
43
|
+
@column({
|
|
44
|
+
primary: true, type: "string", beforeSave(value) {
|
|
45
|
+
return value ?? uuidv4();
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
id!: string;
|
|
49
|
+
|
|
50
|
+
@column({ type: "string", nullable: true })
|
|
51
|
+
organizationId: string|null = null;
|
|
52
|
+
|
|
53
|
+
@column({ type: "string" })
|
|
54
|
+
userId: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The e-mail that will get verified. If on verification, the user e-mail differs from this one,
|
|
58
|
+
* we'll set the user email to this email address
|
|
59
|
+
*/
|
|
60
|
+
@column({ type: "string" })
|
|
61
|
+
email: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* This one is send to the sender. Allows for polling + extra length + sender authentication
|
|
65
|
+
*/
|
|
66
|
+
@column({ type: "string" })
|
|
67
|
+
token = "";
|
|
68
|
+
|
|
69
|
+
@column({ type: "string" })
|
|
70
|
+
code = "";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The amount of times this code has been generated for the same e-mail address
|
|
74
|
+
*/
|
|
75
|
+
@column({ type: "integer" })
|
|
76
|
+
generatedCount = 0;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The amount of times this unique code has been tried.
|
|
80
|
+
*/
|
|
81
|
+
@column({ type: "integer" })
|
|
82
|
+
tries = 0;
|
|
83
|
+
|
|
84
|
+
@column({ type: "datetime" })
|
|
85
|
+
expiresAt: Date;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* createdAt behaves more like createdAt for verificationCode. Since every save is considered to have a new verificationCode
|
|
89
|
+
*/
|
|
90
|
+
@column({
|
|
91
|
+
type: "datetime", beforeSave() {
|
|
92
|
+
const date = new Date()
|
|
93
|
+
date.setMilliseconds(0)
|
|
94
|
+
return date
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
createdAt: Date
|
|
98
|
+
|
|
99
|
+
static CODE_LENGTH = 6
|
|
100
|
+
static MAX_TRIES = 9 // minus 0..MAX_TRIES_VARY
|
|
101
|
+
|
|
102
|
+
async generateCode() {
|
|
103
|
+
this.code = ((await randomInt(Math.pow(10, EmailVerificationCode.CODE_LENGTH)))+"").padStart(EmailVerificationCode.CODE_LENGTH, "0")
|
|
104
|
+
this.token = bs58.encode(await randomBytes(100)).toLowerCase();
|
|
105
|
+
|
|
106
|
+
// Increase generatedCount if we changed the code
|
|
107
|
+
if (this.tries > 0) {
|
|
108
|
+
// For statistics (how many did they try and had to resend the email)
|
|
109
|
+
this.generatedCount++;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Reset the real tries
|
|
113
|
+
this.tries = 0
|
|
114
|
+
|
|
115
|
+
// Expire in 12 hours
|
|
116
|
+
this.expiresAt = new Date(new Date().getTime() + 1000 * 60 * 60 * 12)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getEmailVerificationUrl(user: import('./User').User, organization: import('./Organization').Organization|null, i18n: I18n) {
|
|
120
|
+
let host: string;
|
|
121
|
+
if (user.permissions || !organization || STAMHOOFD.userMode === 'platform') {
|
|
122
|
+
host = "https://"+(STAMHOOFD.domains.dashboard ?? "stamhoofd.app")+"/"+i18n.locale
|
|
123
|
+
} else {
|
|
124
|
+
// Add language if different than default
|
|
125
|
+
host = "https://"+organization.getHost()
|
|
126
|
+
|
|
127
|
+
if (i18n.language != organization.i18n.language) {
|
|
128
|
+
host += "/"+i18n.language
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return host+"/verify-email"+(user.organizationPermissions && this.organizationId ? "/"+encodeURIComponent(this.organizationId) : "")+"?code="+encodeURIComponent(this.code)+"&token="+encodeURIComponent(this.token);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Return true if this token is still valid (used for automatic polling in code view)
|
|
137
|
+
*/
|
|
138
|
+
static async poll(organizationId: string|null, token: string): Promise<boolean> {
|
|
139
|
+
const verificationCodes = await this.where({
|
|
140
|
+
token,
|
|
141
|
+
organizationId: {
|
|
142
|
+
sign: 'IN',
|
|
143
|
+
value: [organizationId, null]
|
|
144
|
+
}
|
|
145
|
+
}, { limit: 1 })
|
|
146
|
+
|
|
147
|
+
if (verificationCodes.length == 0) {
|
|
148
|
+
return false // = expired or invalid
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const verificationCode = verificationCodes[0]
|
|
152
|
+
|
|
153
|
+
if (verificationCode.token !== token) {
|
|
154
|
+
// Safety check, is not possible
|
|
155
|
+
console.error("Security check failed for verify: check MySQL optimization")
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (verificationCode.expiresAt < new Date()) {
|
|
160
|
+
// Expired.
|
|
161
|
+
// Can't expose this because that would expose a user enumeration attack
|
|
162
|
+
// -> we'll include this expiry date in e-mails
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (verificationCode.tries >= EmailVerificationCode.MAX_TRIES) {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return true
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* We don't throw errors here, because we don't want to expose any information about the existance of the code.
|
|
175
|
+
* We just expose if it is valid or not, nothing else
|
|
176
|
+
* False = expired or invalid
|
|
177
|
+
* Error => token okay, but too many attempts checking the code
|
|
178
|
+
*
|
|
179
|
+
*/
|
|
180
|
+
static async verify(organizationId: string|null, token: string, code: string): Promise<EmailVerificationCode | undefined> {
|
|
181
|
+
if (code.length != EmailVerificationCode.CODE_LENGTH) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const verificationCodes = await this.where({
|
|
186
|
+
token,
|
|
187
|
+
organizationId: {
|
|
188
|
+
sign: 'IN',
|
|
189
|
+
value: [organizationId, null]
|
|
190
|
+
}
|
|
191
|
+
}, { limit: 1 })
|
|
192
|
+
|
|
193
|
+
if (verificationCodes.length == 0) {
|
|
194
|
+
return // = expired or invalid
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const verificationCode = verificationCodes[0]
|
|
198
|
+
|
|
199
|
+
if (verificationCode.token !== token) {
|
|
200
|
+
// Safety check, is not possible
|
|
201
|
+
console.error("Security check failed for verify: check MySQL optimization")
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (verificationCode.expiresAt < new Date()) {
|
|
206
|
+
// Expired.
|
|
207
|
+
// Can't expose this because that would expose a user enumeration attack
|
|
208
|
+
// -> we'll include this expiry date in e-mails
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (verificationCode.tries >= EmailVerificationCode.MAX_TRIES) {
|
|
213
|
+
// We can saferly inform the user, because he is authenticated with the token
|
|
214
|
+
throw new SimpleError({
|
|
215
|
+
code: "too_many_attempts",
|
|
216
|
+
message: "Too many attempts",
|
|
217
|
+
human: "Je hebt de code te veel foutief ingegeven. Verstuur eerst een nieuwe code voor je opnieuw probeert.",
|
|
218
|
+
statusCode: 429
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (verificationCode.code === code || (code === "111111" && STAMHOOFD.environment === "development")) {
|
|
223
|
+
// Delete all remaining information!
|
|
224
|
+
// To avoid leaving information about the existince of this user (tries)
|
|
225
|
+
await verificationCode.delete()
|
|
226
|
+
|
|
227
|
+
return verificationCode
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
verificationCode.tries++
|
|
231
|
+
await verificationCode.save()
|
|
232
|
+
|
|
233
|
+
if (verificationCode.tries >= EmailVerificationCode.MAX_TRIES) {
|
|
234
|
+
// We can saferly inform the user, because he is authenticated with the token
|
|
235
|
+
throw new SimpleError({
|
|
236
|
+
code: "too_many_attempts",
|
|
237
|
+
message: "Too many attempts",
|
|
238
|
+
human: "Je hebt de code te veel foutief ingegeven. Verstuur eerst een nieuwe code voor je opnieuw probeert.",
|
|
239
|
+
statusCode: 429
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
send(user: import('./User').User, organization: import('./Organization').Organization|null, i18n: I18n, withCode = true) {
|
|
245
|
+
const { from, replyTo } = {
|
|
246
|
+
from: (user.organizationPermissions || !organization ? Email.getInternalEmailFor(i18n) : organization.getStrongEmail(i18n)),
|
|
247
|
+
replyTo: undefined // Don't use replyTo because it affects deliverability rates due to spam filters
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const url = this.getEmailVerificationUrl(user, organization, i18n)
|
|
251
|
+
|
|
252
|
+
const footer = (!user.organizationPermissions && organization ? "\n\n—\n\nOnze ledenadministratie werkt via het Stamhoofd platform, op maat van verenigingen. Probeer het ook via https://"+i18n.$t("shared.domains.marketing")+"/ledenadministratie\n\n" : '')
|
|
253
|
+
const footerHTML = (!user.organizationPermissions && organization ? "<br><br>—<br><br>Onze ledenadministratie werkt via het Stamhoofd platform, op maat van verenigingen. Probeer het ook via <a href=\"https://"+i18n.$t("shared.domains.marketing")+"/ledenadministratie\">Stamhoofd</a><br><br>" : '')
|
|
254
|
+
|
|
255
|
+
const name = organization?.name ?? 'Stamhoofd'
|
|
256
|
+
|
|
257
|
+
if (withCode) {
|
|
258
|
+
const formattedCode = this.code.substr(0, 3)+" "+this.code.substr(3)
|
|
259
|
+
Email.send({
|
|
260
|
+
from,
|
|
261
|
+
replyTo,
|
|
262
|
+
to: this.email,
|
|
263
|
+
subject: `[${user.organizationPermissions ? "Stamhoofd" : name}] Verifieer jouw e-mailadres`,
|
|
264
|
+
type: "transactional",
|
|
265
|
+
text: `Hallo${user.firstName ? (" "+user.firstName) : ""}!\n\nVerifieer jouw e-mailadres om te kunnen inloggen bij ${name}. Vul de code "${formattedCode}" in op de website of klik op de onderstaande link om jouw e-mailadres te bevestigen.\n${url}\n\nDit is een automatische e-mail. Gelieve niet op dit e-mailadres te reageren.\n\n${user.organizationPermissions ? "Stamhoofd" : name}`+footer,
|
|
266
|
+
html: `Hallo${user.firstName ? (" "+user.firstName) : ""}!<br><br>Verifieer jouw e-mailadres om te kunnen inloggen bij ${name}. Vul de onderstaande code in op de website<br><br><strong style="font-size: 30px; font-weight: bold;">${formattedCode}</strong><br><br>Of klik op de onderstaande link om jouw e-mailadres te bevestigen:<br>${url}<br><br>Dit is een automatische e-mail. Gelieve niet op dit e-mailadres te reageren.<br><br>${user.organizationPermissions ? "Stamhoofd" : name}`+footerHTML
|
|
267
|
+
})
|
|
268
|
+
} else {
|
|
269
|
+
Email.send({
|
|
270
|
+
from,
|
|
271
|
+
replyTo,
|
|
272
|
+
to: this.email,
|
|
273
|
+
type: "transactional",
|
|
274
|
+
subject: `[${user.organizationPermissions ? "Stamhoofd" : name}] Verifieer jouw e-mailadres`,
|
|
275
|
+
text: `Hallo${user.firstName ? (" "+user.firstName) : ""}!\n\nVerifieer jouw e-mailadres om te kunnen inloggen bij ${name}. Klik op de onderstaande link om jouw e-mailadres te bevestigen.\n${url}\n\nDit is een automatische e-mail. Gelieve niet op dit e-mailadres te reageren.\n\n${user.organizationPermissions ? "Stamhoofd" : name}`+footer
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static async resend(organization: import('./Organization').Organization|null, token: string, i18n: I18n) {
|
|
281
|
+
const verificationCodes = await this.where({
|
|
282
|
+
token,
|
|
283
|
+
organizationId: organization ? {
|
|
284
|
+
sign: 'IN',
|
|
285
|
+
value: [organization.id, null]
|
|
286
|
+
} : null
|
|
287
|
+
}, { limit: 1 })
|
|
288
|
+
|
|
289
|
+
if (verificationCodes.length == 0) {
|
|
290
|
+
console.log("Can't resend code, no coded found for token", token)
|
|
291
|
+
// TODO: maybe send a note via email
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const verificationCode = verificationCodes[0]
|
|
296
|
+
|
|
297
|
+
if (verificationCode.expiresAt < new Date()) {
|
|
298
|
+
// Don't report error, could be brute forced
|
|
299
|
+
console.log("Can't resend code, token is expired", token)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const {User} = await import('./User')
|
|
304
|
+
const user = await User.getByID(verificationCode.userId)
|
|
305
|
+
if (!user) {
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
verificationCode.send(user, organization, i18n)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Create or reuse a verification code for a given email address
|
|
313
|
+
* If needed, it will update the code.
|
|
314
|
+
* Use this method for sending only, not for verification!
|
|
315
|
+
*/
|
|
316
|
+
static async createFor(user: import('./User').User, email: string): Promise<EmailVerificationCode> {
|
|
317
|
+
// TODO: make this constant time to avoid complex timing attacks (especially when under load)
|
|
318
|
+
// Do we already have a verificationCode for this email?
|
|
319
|
+
|
|
320
|
+
// Only user <-> organization binding is unique
|
|
321
|
+
// Since, when changing email, we don't throw an error if it is use (user enumeration)
|
|
322
|
+
// So multiple users should be able to request changing to a password, but only on validation should they fail
|
|
323
|
+
// (or this should be noted in the verification email and accounts could be merged)
|
|
324
|
+
const verificationCodes = await this.where({ userId: user.id }, { limit: 1 })
|
|
325
|
+
|
|
326
|
+
let verificationCode: EmailVerificationCode
|
|
327
|
+
if (verificationCodes.length == 0) {
|
|
328
|
+
verificationCode = new EmailVerificationCode()
|
|
329
|
+
verificationCode.organizationId = user.organizationId
|
|
330
|
+
await verificationCode.generateCode()
|
|
331
|
+
|
|
332
|
+
// Reset the real tries
|
|
333
|
+
verificationCode.tries = 0
|
|
334
|
+
|
|
335
|
+
// Expire in 3 hours
|
|
336
|
+
verificationCode.expiresAt = new Date(new Date().getTime() + 1000 * 60 * 60 * 3)
|
|
337
|
+
} else {
|
|
338
|
+
verificationCode = verificationCodes[0]
|
|
339
|
+
|
|
340
|
+
if (verificationCode.expiresAt < new Date(new Date().getTime() - 15 * 60 * 1000) || verificationCode.tries >= EmailVerificationCode.MAX_TRIES) {
|
|
341
|
+
// Expired: also update the token
|
|
342
|
+
await verificationCode.generateCode()
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
verificationCode.email = email
|
|
347
|
+
verificationCode.userId = user.id
|
|
348
|
+
|
|
349
|
+
await verificationCode.save()
|
|
350
|
+
return verificationCode
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { column, Database, ManyToOneRelation, Model, OneToManyRelation } from '@simonbackx/simple-database';
|
|
2
|
+
import { CycleInformation, Group as GroupStruct, GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus } from '@stamhoofd/structures';
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
|
|
5
|
+
import { Member, MemberWithRegistrations, OrganizationRegistrationPeriod, Payment, Registration, User } from './';
|
|
6
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
7
|
+
|
|
8
|
+
if (Member === undefined) {
|
|
9
|
+
throw new Error("Import Member is undefined")
|
|
10
|
+
}
|
|
11
|
+
if (User === undefined) {
|
|
12
|
+
throw new Error("Import User is undefined")
|
|
13
|
+
}
|
|
14
|
+
if (Payment === undefined) {
|
|
15
|
+
throw new Error("Import Payment is undefined")
|
|
16
|
+
}
|
|
17
|
+
if (Registration === undefined) {
|
|
18
|
+
throw new Error("Import Registration is undefined")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Group extends Model {
|
|
22
|
+
static table = "groups";
|
|
23
|
+
|
|
24
|
+
@column({
|
|
25
|
+
primary: true, type: "string", beforeSave(value) {
|
|
26
|
+
return value ?? uuidv4();
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
id!: string;
|
|
30
|
+
|
|
31
|
+
@column({ type: "json", decoder: GroupSettings })
|
|
32
|
+
settings: GroupSettings;
|
|
33
|
+
|
|
34
|
+
@column({
|
|
35
|
+
type: "json", decoder: GroupPrivateSettings
|
|
36
|
+
})
|
|
37
|
+
privateSettings = GroupPrivateSettings.create({})
|
|
38
|
+
|
|
39
|
+
@column({ type: "string" })
|
|
40
|
+
organizationId: string;
|
|
41
|
+
|
|
42
|
+
@column({ type: "string" })
|
|
43
|
+
periodId: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Every time a new registration period starts, this number increases. This is used to mark all older registrations as 'out of date' automatically
|
|
47
|
+
*/
|
|
48
|
+
@column({ type: "integer" })
|
|
49
|
+
cycle = 0;
|
|
50
|
+
|
|
51
|
+
@column({
|
|
52
|
+
type: "datetime", beforeSave(old?: any) {
|
|
53
|
+
if (old !== undefined) {
|
|
54
|
+
return old;
|
|
55
|
+
}
|
|
56
|
+
const date = new Date()
|
|
57
|
+
date.setMilliseconds(0)
|
|
58
|
+
return date
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
createdAt: Date
|
|
62
|
+
|
|
63
|
+
@column({
|
|
64
|
+
type: "datetime", beforeSave() {
|
|
65
|
+
const date = new Date()
|
|
66
|
+
date.setMilliseconds(0)
|
|
67
|
+
return date
|
|
68
|
+
},
|
|
69
|
+
skipUpdate: true
|
|
70
|
+
})
|
|
71
|
+
updatedAt: Date
|
|
72
|
+
|
|
73
|
+
@column({
|
|
74
|
+
type: "datetime",
|
|
75
|
+
nullable: true
|
|
76
|
+
})
|
|
77
|
+
deletedAt: Date | null = null
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Every time a new registration period starts, this number increases. This is used to mark all older registrations as 'out of date' automatically
|
|
81
|
+
*/
|
|
82
|
+
@column({ type: "string" })
|
|
83
|
+
status = GroupStatus.Open;
|
|
84
|
+
|
|
85
|
+
static async getAll(organizationId: string, periodId: string|null, active = true) {
|
|
86
|
+
const w: any = periodId ? {periodId} : {}
|
|
87
|
+
if (active) {
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
89
|
+
return await Group.where({ organizationId, deletedAt: null, ...w, status: {sign: '!=', value: GroupStatus.Archived} })
|
|
90
|
+
}
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
92
|
+
return await Group.where({ organizationId, ...w })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns all parent and grandparents of this group
|
|
97
|
+
*/
|
|
98
|
+
getParentCategories(all: GroupCategory[], recursive = true): GroupCategory[] {
|
|
99
|
+
const map = new Map<string, GroupCategory>()
|
|
100
|
+
|
|
101
|
+
const parents = all.filter(g => g.groupIds.includes(this.id))
|
|
102
|
+
for (const parent of parents) {
|
|
103
|
+
map.set(parent.id, parent)
|
|
104
|
+
|
|
105
|
+
if (recursive) {
|
|
106
|
+
const hisParents = parent.getParentCategories(all)
|
|
107
|
+
for (const pp of hisParents) {
|
|
108
|
+
map.set(pp.id, pp)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return [...map.values()]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Fetch all members with their corresponding (valid) registrations, users
|
|
118
|
+
*/
|
|
119
|
+
async getMembersWithRegistration(waitingList = false, cycleOffset = 0): Promise<MemberWithRegistrations[]> {
|
|
120
|
+
let query = `SELECT ${Member.getDefaultSelect()}, ${Registration.getDefaultSelect()}, ${User.getDefaultSelect()} from \`${Member.table}\`\n`;
|
|
121
|
+
|
|
122
|
+
query += `JOIN \`${Registration.table}\` ON \`${Registration.table}\`.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND (\`${Registration.table}\`.\`registeredAt\` is not null OR \`${Registration.table}\`.\`waitingList\` = 1)\n`
|
|
123
|
+
|
|
124
|
+
if (waitingList) {
|
|
125
|
+
query += `JOIN \`${Registration.table}\` as reg_filter ON reg_filter.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND reg_filter.\`waitingList\` = 1\n`
|
|
126
|
+
} else {
|
|
127
|
+
query += `JOIN \`${Registration.table}\` as reg_filter ON reg_filter.\`${Member.registrations.foreignKey}\` = \`${Member.table}\`.\`${Member.primary.name}\` AND reg_filter.\`waitingList\` = 0 AND reg_filter.\`registeredAt\` is not null\n`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
query += Member.users.joinQuery(Member.table, User.table)+"\n"
|
|
131
|
+
|
|
132
|
+
// We do an extra join because we also need to get the other registrations of each member (only one regitration has to match the query)
|
|
133
|
+
query += `where reg_filter.\`groupId\` = ? AND reg_filter.\`cycle\` = ?`
|
|
134
|
+
|
|
135
|
+
const [results] = await Database.select(query, [this.id, this.cycle - cycleOffset])
|
|
136
|
+
const members: MemberWithRegistrations[] = []
|
|
137
|
+
|
|
138
|
+
const groupIds = results.map(r => r[Registration.table]?.groupId).filter(id => id) as string[]
|
|
139
|
+
const groups = await Group.getByIDs(...Formatter.uniqueArray(groupIds))
|
|
140
|
+
|
|
141
|
+
for (const row of results) {
|
|
142
|
+
const foundMember = Member.fromRow(row[Member.table])
|
|
143
|
+
if (!foundMember) {
|
|
144
|
+
throw new Error("Expected member in every row")
|
|
145
|
+
}
|
|
146
|
+
const _f = foundMember.setManyRelation(Member.registrations as unknown as OneToManyRelation<"registrations", Member, Registration & {group: Group}>, []).setManyRelation(Member.users, [])
|
|
147
|
+
|
|
148
|
+
// Seach if we already got this member?
|
|
149
|
+
const existingMember = members.find(m => m.id == _f.id)
|
|
150
|
+
|
|
151
|
+
const member: MemberWithRegistrations = (existingMember ?? _f)
|
|
152
|
+
if (!existingMember) {
|
|
153
|
+
members.push(member)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if we have a registration with a payment
|
|
157
|
+
const registration = Registration.fromRow(row[Registration.table])
|
|
158
|
+
if (registration) {
|
|
159
|
+
// Check if we already have this registration
|
|
160
|
+
if (!member.registrations.find(r => r.id == registration.id)) {
|
|
161
|
+
const group = groups.find(g => g.id == registration.groupId)
|
|
162
|
+
if (!group) {
|
|
163
|
+
throw new Error("Expected group")
|
|
164
|
+
}
|
|
165
|
+
member.registrations.push(registration.setRelation(Registration.group, group))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check if we have a user
|
|
170
|
+
const user = User.fromRow(row[User.table])
|
|
171
|
+
if (user) {
|
|
172
|
+
// Check if we already have this registration
|
|
173
|
+
if (!member.users.find(r => r.id == user.id)) {
|
|
174
|
+
member.users.push(user)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return members
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getStructure() {
|
|
184
|
+
return GroupStruct.create({ ...this, privateSettings: null })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getPrivateStructure() {
|
|
188
|
+
return GroupStruct.create(this)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private static async getCount(where: string, params: any[]) {
|
|
192
|
+
const query = `select count(*) as c from \`${Registration.table}\` where ${where}`
|
|
193
|
+
|
|
194
|
+
const [results] = await Database.select(query, params)
|
|
195
|
+
const count = results[0]['']['c'];
|
|
196
|
+
if (Number.isInteger(count)) {
|
|
197
|
+
return count as number
|
|
198
|
+
}
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async updateOccupancy() {
|
|
203
|
+
this.settings.registeredMembers = await Group.getCount(
|
|
204
|
+
"groupId = ? and cycle = ? and waitingList = 0 and registeredAt is not null",
|
|
205
|
+
[this.id, this.cycle]
|
|
206
|
+
)
|
|
207
|
+
//const query = `select count(*) as c from \`${Registration.table}\` where groupId = ? and cycle = ? and (((registeredAt is not null or reservedUntil >= ?) and waitingList = 0) OR (waitingList = 1 AND canRegister = 1))`
|
|
208
|
+
|
|
209
|
+
this.settings.reservedMembers = await Group.getCount(
|
|
210
|
+
"groupId = ? and cycle = ? and ((waitingList = 0 and registeredAt is null AND reservedUntil >= ?) OR (waitingList = 1 and canRegister = 1))",
|
|
211
|
+
[this.id, this.cycle, new Date()]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
this.settings.waitingListSize = await Group.getCount(
|
|
215
|
+
"groupId = ? and cycle = ? and waitingList = 1",
|
|
216
|
+
[this.id, this.cycle, new Date()]
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
// Loop cycle -1 until current (excluding current)
|
|
220
|
+
for (let cycle = -1; cycle < this.cycle; cycle++) {
|
|
221
|
+
if (!this.settings.cycleSettings.has(cycle)) {
|
|
222
|
+
this.settings.cycleSettings.set(cycle, CycleInformation.create({
|
|
223
|
+
registeredMembers: 0,
|
|
224
|
+
reservedMembers: 0,
|
|
225
|
+
waitingListSize: 0
|
|
226
|
+
}))
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Older cycles
|
|
231
|
+
// todo: optimize this a bit
|
|
232
|
+
for (const [cycle, info] of this.settings.cycleSettings) {
|
|
233
|
+
|
|
234
|
+
info.registeredMembers = await Group.getCount(
|
|
235
|
+
"groupId = ? and cycle = ? and waitingList = 0 and registeredAt is not null",
|
|
236
|
+
[this.id, cycle]
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
info.reservedMembers = await Group.getCount(
|
|
240
|
+
"groupId = ? and cycle = ? and ((waitingList = 0 and registeredAt is null AND reservedUntil >= ?) OR (waitingList = 1 and canRegister = 1))",
|
|
241
|
+
[this.id, cycle, new Date()]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
info.waitingListSize = await Group.getCount(
|
|
245
|
+
"groupId = ? and cycle = ? and waitingList = 1",
|
|
246
|
+
[this.id, cycle, new Date()]
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static async deleteUnreachable(organizationId: string, period: OrganizationRegistrationPeriod, allGroups: Group[]) {
|
|
252
|
+
const reachable = new Map<string, boolean>()
|
|
253
|
+
|
|
254
|
+
const visited = new Map<string, boolean>()
|
|
255
|
+
const queue = [period.settings.rootCategoryId]
|
|
256
|
+
visited.set(period.settings.rootCategoryId, true)
|
|
257
|
+
|
|
258
|
+
while (queue.length > 0) {
|
|
259
|
+
const id = queue.shift()
|
|
260
|
+
if (!id) {
|
|
261
|
+
break
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const category = period.settings.categories.find(c => c.id === id)
|
|
265
|
+
if (!category) {
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const i of category.categoryIds) {
|
|
270
|
+
if (!visited.get(i)) {
|
|
271
|
+
queue.push(i)
|
|
272
|
+
visited.set(i, true)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for (const g of category.groupIds) {
|
|
277
|
+
reachable.set(g, true)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const group of allGroups) {
|
|
282
|
+
if (!reachable.get(group.id) && group.status !== GroupStatus.Archived) {
|
|
283
|
+
console.log("Archiving unreachable group "+group.id+" from organization "+organizationId + " org period "+period.id)
|
|
284
|
+
group.status = GroupStatus.Archived
|
|
285
|
+
await group.save()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
Registration.group = new ManyToOneRelation(Group, "group")
|
|
293
|
+
Registration.group.foreignKey = "groupId"
|