@stamhoofd/models 2.79.8 → 2.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/src/factories/EmailTemplateFactory.d.ts +18 -0
  2. package/dist/src/factories/EmailTemplateFactory.d.ts.map +1 -0
  3. package/dist/src/factories/EmailTemplateFactory.js +43 -0
  4. package/dist/src/factories/EmailTemplateFactory.js.map +1 -0
  5. package/dist/src/factories/EventFactory.d.ts +17 -0
  6. package/dist/src/factories/EventFactory.d.ts.map +1 -0
  7. package/dist/src/factories/EventFactory.js +30 -0
  8. package/dist/src/factories/EventFactory.js.map +1 -0
  9. package/dist/src/factories/EventNotificationFactory.d.ts +21 -0
  10. package/dist/src/factories/EventNotificationFactory.d.ts.map +1 -0
  11. package/dist/src/factories/EventNotificationFactory.js +44 -0
  12. package/dist/src/factories/EventNotificationFactory.js.map +1 -0
  13. package/dist/src/factories/EventNotificationTypeFactory.d.ts +11 -0
  14. package/dist/src/factories/EventNotificationTypeFactory.d.ts.map +1 -0
  15. package/dist/src/factories/EventNotificationTypeFactory.js +25 -0
  16. package/dist/src/factories/EventNotificationTypeFactory.js.map +1 -0
  17. package/dist/src/factories/PlatformEventTypeFactory.d.ts +10 -0
  18. package/dist/src/factories/PlatformEventTypeFactory.d.ts.map +1 -0
  19. package/dist/src/factories/PlatformEventTypeFactory.js +23 -0
  20. package/dist/src/factories/PlatformEventTypeFactory.js.map +1 -0
  21. package/dist/src/factories/RecordAnswerFactory.d.ts +17 -0
  22. package/dist/src/factories/RecordAnswerFactory.d.ts.map +1 -0
  23. package/dist/src/factories/RecordAnswerFactory.js +95 -0
  24. package/dist/src/factories/RecordAnswerFactory.js.map +1 -0
  25. package/dist/src/factories/RecordCategoryFactory.d.ts +11 -0
  26. package/dist/src/factories/RecordCategoryFactory.d.ts.map +1 -0
  27. package/dist/src/factories/RecordCategoryFactory.js +23 -0
  28. package/dist/src/factories/RecordCategoryFactory.js.map +1 -0
  29. package/dist/src/factories/RecordFactory.d.ts +7 -6
  30. package/dist/src/factories/RecordFactory.d.ts.map +1 -1
  31. package/dist/src/factories/RecordFactory.js +10 -4
  32. package/dist/src/factories/RecordFactory.js.map +1 -1
  33. package/dist/src/factories/RegistrationPeriodFactory.d.ts +1 -0
  34. package/dist/src/factories/RegistrationPeriodFactory.d.ts.map +1 -1
  35. package/dist/src/factories/RegistrationPeriodFactory.js +2 -0
  36. package/dist/src/factories/RegistrationPeriodFactory.js.map +1 -1
  37. package/dist/src/factories/index.d.ts +7 -0
  38. package/dist/src/factories/index.d.ts.map +1 -1
  39. package/dist/src/factories/index.js +7 -0
  40. package/dist/src/factories/index.js.map +1 -1
  41. package/dist/src/helpers/DNSValidator.d.ts.map +1 -1
  42. package/dist/src/helpers/DNSValidator.js +57 -55
  43. package/dist/src/helpers/DNSValidator.js.map +1 -1
  44. package/dist/src/helpers/EmailBuilder.d.ts.map +1 -1
  45. package/dist/src/helpers/EmailBuilder.js +2 -1
  46. package/dist/src/helpers/EmailBuilder.js.map +1 -1
  47. package/dist/src/migrations/1741954610-event-notifications-accepted-record-answers.sql +2 -0
  48. package/dist/src/migrations/1742393831-default-template-partially-accepted-event-notification.sql +2 -0
  49. package/dist/src/migrations/1742579832-audit-log-longer-object-size.sql +3 -0
  50. package/dist/src/models/EventNotification.d.ts +4 -0
  51. package/dist/src/models/EventNotification.d.ts.map +1 -1
  52. package/dist/src/models/EventNotification.js +7 -0
  53. package/dist/src/models/EventNotification.js.map +1 -1
  54. package/dist/src/models/Organization.d.ts +1 -0
  55. package/dist/src/models/Organization.d.ts.map +1 -1
  56. package/dist/src/models/Organization.js +13 -4
  57. package/dist/src/models/Organization.js.map +1 -1
  58. package/dist/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +2 -2
  60. package/src/factories/EmailTemplateFactory.ts +47 -0
  61. package/src/factories/EventFactory.ts +31 -0
  62. package/src/factories/EventNotificationFactory.ts +51 -0
  63. package/src/factories/EventNotificationTypeFactory.ts +26 -0
  64. package/src/factories/PlatformEventTypeFactory.ts +23 -0
  65. package/src/factories/RecordAnswerFactory.ts +113 -0
  66. package/src/factories/RecordCategoryFactory.ts +23 -0
  67. package/src/factories/RecordFactory.ts +14 -8
  68. package/src/factories/RegistrationPeriodFactory.ts +2 -0
  69. package/src/factories/index.ts +7 -0
  70. package/src/helpers/DNSValidator.ts +62 -56
  71. package/src/helpers/EmailBuilder.ts +2 -1
  72. package/src/migrations/1741954610-event-notifications-accepted-record-answers.sql +2 -0
  73. package/src/migrations/1742393831-default-template-partially-accepted-event-notification.sql +2 -0
  74. package/src/migrations/1742579832-audit-log-longer-object-size.sql +3 -0
  75. package/src/models/EventNotification.ts +6 -0
  76. package/src/models/Organization.ts +15 -5
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/factories/addressfactory.ts","../src/factories/balanceitemfactory.ts","../src/factories/emergencycontactfactory.ts","../src/factories/groupfactory.ts","../src/factories/memberfactory.ts","../src/factories/memberresponsibilityrecordfactory.ts","../src/factories/organizationfactory.ts","../src/factories/organizationregistrationperiodfactory.ts","../src/factories/organizationtagfactory.ts","../src/factories/parentfactory.ts","../src/factories/platformresponsibilityfactory.ts","../src/factories/recordfactory.ts","../src/factories/registercodefactory.ts","../src/factories/registrationfactory.ts","../src/factories/registrationperiodfactory.ts","../src/factories/userfactory.ts","../src/factories/webshopfactory.ts","../src/factories/index.ts","../src/helpers/dnsvalidator.ts","../src/helpers/emailbuilder.ts","../src/helpers/groupbuilder.ts","../src/helpers/handlebars.ts","../src/helpers/membermerger.test.ts","../src/helpers/membermerger.ts","../src/helpers/ratelimiter.ts","../src/helpers/webshopcounter.ts","../src/migrations/1605262045-import-postcodes.ts","../src/migrations/1605262046-import-postcodes-nl.ts","../src/models/auditlog.ts","../src/models/balanceitem.ts","../src/models/balanceitempayment.ts","../src/models/buckaroopayment.ts","../src/models/cachedbalance.ts","../src/models/document.ts","../src/models/documenttemplate.ts","../src/models/email.test.ts","../src/models/email.ts","../src/models/emailrecipient.ts","../src/models/emailtemplate.ts","../src/models/emailverificationcode.ts","../src/models/event.ts","../src/models/eventnotification.ts","../src/models/group.ts","../src/models/image.ts","../src/models/member.ts","../src/models/memberplatformmembership.ts","../src/models/memberresponsibilityrecord.ts","../src/models/memberuser.ts","../src/models/mergedmember.ts","../src/models/molliepayment.ts","../src/models/mollietoken.ts","../src/models/onetimetoken.ts","../src/models/order.ts","../src/models/organization.ts","../src/models/organizationregistrationperiod.ts","../src/models/passwordtoken.ts","../src/models/payconiqpayment.ts","../src/models/payment.ts","../src/models/platform.test.ts","../src/models/platform.ts","../src/models/registercode.ts","../src/models/registration.ts","../src/models/registrationperiod.ts","../src/models/stcredit.ts","../src/models/stinvoice.ts","../src/models/stpackage.ts","../src/models/stpendinginvoice.ts","../src/models/stripeaccount.ts","../src/models/stripecheckoutsession.ts","../src/models/stripepaymentintent.ts","../src/models/ticket.ts","../src/models/token.test.ts","../src/models/token.ts","../src/models/usedregistercode.ts","../src/models/user.ts","../src/models/userpermissions.ts","../src/models/webshop.ts","../src/models/webshopdiscountcode.ts","../src/models/index.ts","../src/models/addresses/city.ts","../src/models/addresses/postalcode.test.ts","../src/models/addresses/postalcode.ts","../src/models/addresses/province.ts","../src/models/addresses/street.ts","../src/structures/organizationservermetadata.ts","../tests/jest.global.setup.ts","../tests/jest.setup.ts","../../../../environment.d.ts","../../../../jest-extended.d.ts","../../../stamhoofd.d.ts"],"version":"5.6.2"}
1
+ {"root":["../src/index.ts","../src/factories/addressfactory.ts","../src/factories/balanceitemfactory.ts","../src/factories/emailtemplatefactory.ts","../src/factories/emergencycontactfactory.ts","../src/factories/eventfactory.ts","../src/factories/eventnotificationfactory.ts","../src/factories/eventnotificationtypefactory.ts","../src/factories/groupfactory.ts","../src/factories/memberfactory.ts","../src/factories/memberresponsibilityrecordfactory.ts","../src/factories/organizationfactory.ts","../src/factories/organizationregistrationperiodfactory.ts","../src/factories/organizationtagfactory.ts","../src/factories/parentfactory.ts","../src/factories/platformeventtypefactory.ts","../src/factories/platformresponsibilityfactory.ts","../src/factories/recordanswerfactory.ts","../src/factories/recordcategoryfactory.ts","../src/factories/recordfactory.ts","../src/factories/registercodefactory.ts","../src/factories/registrationfactory.ts","../src/factories/registrationperiodfactory.ts","../src/factories/userfactory.ts","../src/factories/webshopfactory.ts","../src/factories/index.ts","../src/helpers/dnsvalidator.ts","../src/helpers/emailbuilder.ts","../src/helpers/groupbuilder.ts","../src/helpers/handlebars.ts","../src/helpers/membermerger.test.ts","../src/helpers/membermerger.ts","../src/helpers/ratelimiter.ts","../src/helpers/webshopcounter.ts","../src/migrations/1605262045-import-postcodes.ts","../src/migrations/1605262046-import-postcodes-nl.ts","../src/models/auditlog.ts","../src/models/balanceitem.ts","../src/models/balanceitempayment.ts","../src/models/buckaroopayment.ts","../src/models/cachedbalance.ts","../src/models/document.ts","../src/models/documenttemplate.ts","../src/models/email.test.ts","../src/models/email.ts","../src/models/emailrecipient.ts","../src/models/emailtemplate.ts","../src/models/emailverificationcode.ts","../src/models/event.ts","../src/models/eventnotification.ts","../src/models/group.ts","../src/models/image.ts","../src/models/member.ts","../src/models/memberplatformmembership.ts","../src/models/memberresponsibilityrecord.ts","../src/models/memberuser.ts","../src/models/mergedmember.ts","../src/models/molliepayment.ts","../src/models/mollietoken.ts","../src/models/onetimetoken.ts","../src/models/order.ts","../src/models/organization.ts","../src/models/organizationregistrationperiod.ts","../src/models/passwordtoken.ts","../src/models/payconiqpayment.ts","../src/models/payment.ts","../src/models/platform.test.ts","../src/models/platform.ts","../src/models/registercode.ts","../src/models/registration.ts","../src/models/registrationperiod.ts","../src/models/stcredit.ts","../src/models/stinvoice.ts","../src/models/stpackage.ts","../src/models/stpendinginvoice.ts","../src/models/stripeaccount.ts","../src/models/stripecheckoutsession.ts","../src/models/stripepaymentintent.ts","../src/models/ticket.ts","../src/models/token.test.ts","../src/models/token.ts","../src/models/usedregistercode.ts","../src/models/user.ts","../src/models/userpermissions.ts","../src/models/webshop.ts","../src/models/webshopdiscountcode.ts","../src/models/index.ts","../src/models/addresses/city.ts","../src/models/addresses/postalcode.test.ts","../src/models/addresses/postalcode.ts","../src/models/addresses/province.ts","../src/models/addresses/street.ts","../src/structures/organizationservermetadata.ts","../tests/jest.global.setup.ts","../tests/jest.setup.ts","../../../../environment.d.ts","../../../../jest-extended.d.ts","../../../stamhoofd.d.ts"],"version":"5.6.2"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/models",
3
- "version": "2.79.8",
3
+ "version": "2.80.0",
4
4
  "main": "./dist/src/index.js",
5
5
  "types": "./dist/src/index.d.ts",
6
6
  "license": "UNLICENCED",
@@ -30,5 +30,5 @@
30
30
  "publishConfig": {
31
31
  "access": "public"
32
32
  },
33
- "gitHead": "06e4690b6413a21c3466d6ab76da3a883e1441c8"
33
+ "gitHead": "252faf1dbd8ebdd8469810e9c51e510d93f21fc2"
34
34
  }
@@ -0,0 +1,47 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+
3
+ import { EmailTemplate } from '../models';
4
+ import { Organization } from '../models/Organization';
5
+ import { EmailTemplateType, EmailTemplate as EmailTemplateStruct } from '@stamhoofd/structures';
6
+
7
+ class Options {
8
+ organization?: Organization;
9
+ groupId?: string;
10
+ webshopId?: string;
11
+ type: EmailTemplateType;
12
+
13
+ subject?: string;
14
+ html?: string;
15
+ text?: string;
16
+ }
17
+
18
+ export class EmailTemplateFactory extends Factory<Options, EmailTemplate> {
19
+ async create(): Promise<EmailTemplate> {
20
+ const template = new EmailTemplate();
21
+
22
+ template.organizationId = this.options.organization?.id ?? null;
23
+ template.groupId = this.options.groupId ?? null;
24
+ template.webshopId = this.options.webshopId ?? null;
25
+ template.type = this.options.type;
26
+
27
+ template.subject = this.options.subject ?? this.options.type;
28
+ template.json = {};
29
+ template.html = '';
30
+ template.text = '';
31
+
32
+ if (this.options.html || this.options.text) {
33
+ template.html = this.options.html ?? '<p></p>';
34
+ template.text = this.options.text ?? 'Text';
35
+ }
36
+ else {
37
+ // Automatically add all replacements in our template email
38
+ for (const replacement of EmailTemplateStruct.getSupportedReplacementsForType(this.options.type)) {
39
+ template.html += `<p>${replacement.token}: {{${replacement.token}}}</p>`;
40
+ template.text += `${replacement.token}: {{${replacement.token}}}\n`;
41
+ }
42
+ }
43
+
44
+ await template.save();
45
+ return template;
46
+ }
47
+ }
@@ -0,0 +1,31 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+ import { EventMeta } from '@stamhoofd/structures';
3
+
4
+ import { Event } from '../models/Event';
5
+ import { Organization } from '../models/Organization';
6
+ import { PlatformEventTypeFactory } from './PlatformEventTypeFactory';
7
+
8
+ class Options {
9
+ organization?: Organization;
10
+ name?: string;
11
+ meta?: EventMeta;
12
+ startDate?: Date;
13
+ endDate?: Date;
14
+ typeId?: string;
15
+ }
16
+
17
+ export class EventFactory extends Factory<Options, Event> {
18
+ async create(): Promise<Event> {
19
+ const event = new Event();
20
+
21
+ event.organizationId = this.options.organization?.id ?? null;
22
+ event.typeId = this.options.typeId ?? (await new PlatformEventTypeFactory({}).create()).id;
23
+ event.startDate = this.options.startDate ?? new Date(Date.now() + 5 * 24 * 60 * 60 * 1000);
24
+ event.endDate = this.options.endDate ?? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
25
+ event.name = this.options.name ?? 'Event ' + (new Date().getTime() + Math.floor(Math.random() * 999999));
26
+ event.meta = this.options.meta ?? EventMeta.create({});
27
+
28
+ await event.save();
29
+ return event;
30
+ }
31
+ }
@@ -0,0 +1,51 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+ import { EventNotificationStatus, RecordAnswer } from '@stamhoofd/structures';
3
+
4
+ import { EventNotification, User } from '../models';
5
+ import { Event } from '../models/Event';
6
+ import { Organization } from '../models/Organization';
7
+ import { EventFactory } from './EventFactory';
8
+ import { EventNotificationTypeFactory } from './EventNotificationTypeFactory';
9
+
10
+ class Options {
11
+ organization: Organization;
12
+ events?: Event[];
13
+ typeId?: string;
14
+ status?: EventNotificationStatus;
15
+ feedbackText?: string;
16
+ periodId?: string;
17
+ recordAnswers?: Map<string, RecordAnswer>;
18
+ acceptedRecordAnswers?: Map<string, RecordAnswer>;
19
+ submittedBy?: User | null;
20
+ }
21
+
22
+ export class EventNotificationFactory extends Factory<Options, EventNotification> {
23
+ async create(): Promise<EventNotification> {
24
+ const eventNotification = new EventNotification();
25
+
26
+ eventNotification.organizationId = this.options.organization.id;
27
+ eventNotification.typeId = this.options.typeId ?? (await new EventNotificationTypeFactory({}).create()).id;
28
+ eventNotification.status = this.options.status ?? EventNotificationStatus.Draft;
29
+ eventNotification.createdBy = null;
30
+ eventNotification.feedbackText = this.options.feedbackText ?? null;
31
+
32
+ const events = this.options.events ?? [await new EventFactory({ organization: this.options.organization }).create()];
33
+
34
+ eventNotification.startDate = events[0].startDate;
35
+ eventNotification.endDate = events[0].endDate;
36
+ eventNotification.periodId = this.options.periodId ?? this.options.organization.periodId;
37
+
38
+ // Set record answers
39
+ eventNotification.recordAnswers = this.options.recordAnswers ?? new Map();
40
+ eventNotification.acceptedRecordAnswers = this.options.acceptedRecordAnswers ?? new Map();
41
+
42
+ eventNotification.submittedBy = this.options.submittedBy?.id ?? null;
43
+ eventNotification.submittedAt = this.options.submittedBy ? new Date() : null;
44
+
45
+ await eventNotification.save();
46
+
47
+ // Link events
48
+ await EventNotification.events.link(eventNotification, events);
49
+ return eventNotification;
50
+ }
51
+ }
@@ -0,0 +1,26 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+ import { EventNotificationType, RecordCategory } from '@stamhoofd/structures';
3
+
4
+ import { Platform } from '../models/Platform';
5
+
6
+ class Options {
7
+ title?: string;
8
+ recordCategories?: RecordCategory[];
9
+ }
10
+
11
+ export class EventNotificationTypeFactory extends Factory<Options, EventNotificationType> {
12
+ async create(): Promise<EventNotificationType> {
13
+ const eventType = EventNotificationType.create({
14
+ title: this.options.title ?? ('Melding ' + (new Date().getTime() + Math.floor(Math.random() * 999999))),
15
+ });
16
+
17
+ eventType.recordCategories = this.options.recordCategories ?? [];
18
+
19
+ // Add to platform
20
+ const platform = await Platform.getForEditing();
21
+ platform.config.eventNotificationTypes.push(eventType);
22
+ await platform.save();
23
+
24
+ return eventType;
25
+ }
26
+ }
@@ -0,0 +1,23 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+ import { PlatformEventType } from '@stamhoofd/structures';
3
+
4
+ import { Platform } from '../models/Platform';
5
+
6
+ class Options {
7
+ name?: string;
8
+ }
9
+
10
+ export class PlatformEventTypeFactory extends Factory<Options, PlatformEventType> {
11
+ async create(): Promise<PlatformEventType> {
12
+ const eventType = PlatformEventType.create({
13
+ name: this.options.name ?? ('Responsibility ' + (new Date().getTime() + Math.floor(Math.random() * 999999))),
14
+ });
15
+
16
+ // Add to platform
17
+ const platform = await Platform.getForEditing();
18
+ platform.config.eventTypes.push(eventType);
19
+ await platform.save();
20
+
21
+ return eventType;
22
+ }
23
+ }
@@ -0,0 +1,113 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+ import { File, Image, RecordAddressAnswer, RecordAnswer, RecordCategory, RecordCheckboxAnswer, RecordChooseOneAnswer, RecordDateAnswer, RecordFileAnswer, RecordImageAnswer, RecordIntegerAnswer, RecordMultipleChoiceAnswer, RecordPriceAnswer, RecordSettings, RecordTextAnswer, RecordType } from '@stamhoofd/structures';
3
+ import { AddressFactory } from './AddressFactory';
4
+
5
+ type Options = ({
6
+ records: RecordSettings[];
7
+ } | {
8
+ recordCategories: RecordCategory[];
9
+ }) & {
10
+ /**
11
+ * Defaults to true
12
+ */
13
+ complete?: boolean;
14
+ };
15
+
16
+ export class RecordAnswerFactory extends Factory<Options, Map<string, RecordAnswer>> {
17
+ async create(): Promise<Map<string, RecordAnswer>> {
18
+ const records = 'records' in this.options ? this.options.records : this.options.recordCategories.flatMap(c => c.getAllRecords());
19
+
20
+ const map: Map<string, RecordAnswer> = new Map();
21
+
22
+ for (const record of records) {
23
+ const answer = RecordAnswer.createDefaultAnswer(record);
24
+
25
+ if (this.options.complete !== false) {
26
+ // Make sure it is filled in
27
+ answer.markReviewed();
28
+
29
+ switch (true) {
30
+ case answer instanceof RecordCheckboxAnswer: {
31
+ // If it is a checkbox, set the first one to true
32
+ answer.selected = true;
33
+ break;
34
+ }
35
+ case answer instanceof RecordTextAnswer: {
36
+ answer.value = 'Test ' + Math.floor(Math.random() * 100000);
37
+
38
+ switch (record.type) {
39
+ case RecordType.Phone:{
40
+ answer.value = '+32 477 77 77 77';
41
+ break;
42
+ }
43
+ case RecordType.Email: {
44
+ answer.value = 'example@example.domain';
45
+ break;
46
+ }
47
+ case RecordType.Textarea: {
48
+ answer.value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nVivamus lacinia odio vitae vestibulum.';
49
+ break;
50
+ }
51
+ }
52
+ break;
53
+ }
54
+
55
+ case answer instanceof RecordMultipleChoiceAnswer: {
56
+ // If it is a multiple choice, select all
57
+ answer.selectedChoices = record.choices.map(c => c.clone());
58
+ break;
59
+ };
60
+
61
+ case answer instanceof RecordChooseOneAnswer: {
62
+ // Select first
63
+ answer.selectedChoice = record.choices[0]?.clone() ?? null;
64
+ break;
65
+ };
66
+
67
+ case answer instanceof RecordAddressAnswer: {
68
+ answer.address = await new AddressFactory({}).create();
69
+ break;
70
+ }
71
+
72
+ case answer instanceof RecordDateAnswer: {
73
+ answer.dateValue = new Date();
74
+ break;
75
+ }
76
+
77
+ case answer instanceof RecordPriceAnswer: {
78
+ answer.value = 1234;
79
+ break;
80
+ }
81
+
82
+ case answer instanceof RecordImageAnswer: {
83
+ answer.image = Image.create({});
84
+ break;
85
+ }
86
+
87
+ case answer instanceof RecordIntegerAnswer: {
88
+ answer.value = 5;
89
+ break;
90
+ }
91
+
92
+ case answer instanceof RecordFileAnswer: {
93
+ answer.file = new File({
94
+ id: 'example-id',
95
+ server: 'https://example.domain',
96
+ path: 'example/path/to/file',
97
+ size: 1234,
98
+ name: 'example.txt',
99
+ isPrivate: false,
100
+ signedUrl: null,
101
+ signature: null,
102
+ });
103
+ break;
104
+ }
105
+ }
106
+ }
107
+
108
+ map.set(record.id, answer);
109
+ }
110
+
111
+ return map;
112
+ }
113
+ }
@@ -0,0 +1,23 @@
1
+ import { Factory } from '@simonbackx/simple-database';
2
+ import { RecordCategory, RecordSettings, RecordType } from '@stamhoofd/structures';
3
+ import { RecordFactory, RecordOptions } from './RecordFactory';
4
+
5
+ class Options {
6
+ records: RecordOptions[];
7
+ }
8
+
9
+ export class RecordCategoryFactory extends Factory<Options, RecordCategory> {
10
+ async create(): Promise<RecordCategory> {
11
+ const records: RecordSettings[] = [];
12
+ for (const record of this.options.records) {
13
+ records.push(
14
+ await new RecordFactory(record).create(),
15
+ );
16
+ }
17
+
18
+ return RecordCategory.create({
19
+ name: 'Record category ' + Math.floor(Math.random() * 10000),
20
+ records,
21
+ });
22
+ }
23
+ }
@@ -1,13 +1,19 @@
1
1
  import { Factory } from '@simonbackx/simple-database';
2
- import { LegacyRecord, LegacyRecordType } from '@stamhoofd/structures';
2
+ import { RecordSettings, RecordType } from '@stamhoofd/structures';
3
3
 
4
- type Options = Record<string, never>;
4
+ export class RecordOptions {
5
+ type: RecordType;
6
+ required?: boolean;
7
+ }
5
8
 
6
- export class RecordFactory extends Factory<Options, LegacyRecord> {
7
- LegacyRecordLegacyRecord;
8
- create(): Promise<LegacyRecord> {
9
- return Promise.resolve(LegacyRecord.create({
10
- type: this.randomArray(Object.values(LegacyRecordType)),
11
- }));
9
+ export class RecordFactory extends Factory<RecordOptions, RecordSettings> {
10
+ create(): Promise<RecordSettings> {
11
+ return Promise.resolve(
12
+ RecordSettings.create({
13
+ name: 'Record name ' + Math.floor(Math.random() * 10000),
14
+ type: this.options.type,
15
+ required: this.options.required ?? (this.options.type !== RecordType.Checkbox),
16
+ }),
17
+ );
12
18
  }
13
19
  }
@@ -7,6 +7,7 @@ class Options {
7
7
  startDate?: Date;
8
8
  endDate?: Date;
9
9
  previousPeriodId?: string;
10
+ locked?: boolean;
10
11
  }
11
12
 
12
13
  export class RegistrationPeriodFactory extends Factory<Options, RegistrationPeriod> {
@@ -20,6 +21,7 @@ export class RegistrationPeriodFactory extends Factory<Options, RegistrationPeri
20
21
  period.previousPeriodId = this.options.previousPeriodId;
21
22
  }
22
23
  period.settings = RegistrationPeriodSettings.create({});
24
+ period.locked = this.options.locked ?? false;
23
25
 
24
26
  await period.save();
25
27
  return period;
@@ -15,3 +15,10 @@ export * from './OrganizationTagFactory';
15
15
  export * from './OrganizationRegistrationPeriodFactory';
16
16
  export * from './MemberResponsibilityRecordFactory';
17
17
  export * from './PlatformResponsibilityFactory';
18
+ export * from './PlatformEventTypeFactory';
19
+ export * from './EventFactory';
20
+ export * from './EventNotificationTypeFactory';
21
+ export * from './EventNotificationFactory';
22
+ export * from './EmailTemplateFactory';
23
+ export * from './RecordCategoryFactory';
24
+ export * from './RecordAnswerFactory';
@@ -20,16 +20,19 @@ export async function validateDNSRecords(dnsRecords: DNSRecord[], didRetry = fal
20
20
  const addresses: string[] = await resolver.resolveCname(record.name.substr(0, record.name.length - 1));
21
21
  record.errors = null;
22
22
 
23
- if (addresses.length == 0) {
23
+ if (addresses.length === 0) {
24
24
  record.status = DNSRecordStatus.Pending;
25
- allValid = false;
26
- hasAllNonTXT = false;
27
25
 
28
- record.errors = new SimpleErrors(new SimpleError({
29
- code: 'not_found',
30
- message: '',
31
- human: 'We konden de CNAME-record ' + record.name + ' nog niet vinden. Hou er rekening mee dat het even (tot 24u) kan duren voor we deze kunnen zien.',
32
- }));
26
+ if (!record.optional) {
27
+ allValid = false;
28
+ hasAllNonTXT = false;
29
+
30
+ record.errors = new SimpleErrors(new SimpleError({
31
+ code: 'not_found',
32
+ message: '',
33
+ human: 'We konden de CNAME-record ' + record.name + ' nog niet vinden. Hou er rekening mee dat het even (tot 24u) kan duren voor we deze kunnen zien.',
34
+ }));
35
+ }
33
36
  }
34
37
  else if (addresses.length > 1) {
35
38
  record.status = DNSRecordStatus.Failed;
@@ -39,7 +42,7 @@ export async function validateDNSRecords(dnsRecords: DNSRecord[], didRetry = fal
39
42
  record.errors = new SimpleErrors(new SimpleError({
40
43
  code: 'too_many_fields',
41
44
  message: '',
42
- human: 'Er zijn meerdere CNAME records ingesteld voor ' + record.name + ', kijk na of je er geen moet verwijderen of per ongeluk meerder hebt aangemaakt',
45
+ human: 'Er zijn meerdere CNAME records ingesteld voor ' + record.name + ', kijk na of je er geen moet verwijderen of per ongeluk meerdere hebt aangemaakt',
43
46
  }));
44
47
  }
45
48
  else {
@@ -48,14 +51,17 @@ export async function validateDNSRecords(dnsRecords: DNSRecord[], didRetry = fal
48
51
  }
49
52
  else {
50
53
  record.status = DNSRecordStatus.Failed;
51
- allValid = false;
52
- hasAllNonTXT = false;
53
54
 
54
- record.errors = new SimpleErrors(new SimpleError({
55
- code: 'wrong_value',
56
- message: '',
57
- human: 'Er is een andere waarde ingesteld voor de CNAME-record ' + record.name + ', kijk na of je geen typfout hebt gemaakt. Gevonden: ' + addresses[0] + '.',
58
- }));
55
+ if (!record.optional) {
56
+ allValid = false;
57
+ hasAllNonTXT = false;
58
+
59
+ record.errors = new SimpleErrors(new SimpleError({
60
+ code: 'wrong_value',
61
+ message: '',
62
+ human: 'Er is een andere waarde ingesteld voor de CNAME-record ' + record.name + ', kijk na of je geen typfout hebt gemaakt. Gevonden: ' + addresses[0] + '.',
63
+ }));
64
+ }
59
65
  }
60
66
  }
61
67
 
@@ -67,15 +73,18 @@ export async function validateDNSRecords(dnsRecords: DNSRecord[], didRetry = fal
67
73
 
68
74
  record.errors = null;
69
75
 
70
- if (records.length == 0) {
76
+ if (records.length === 0) {
71
77
  record.status = DNSRecordStatus.Pending;
72
- allValid = false;
73
78
 
74
- record.errors = new SimpleErrors(new SimpleError({
75
- code: 'not_found',
76
- message: '',
77
- human: 'We konden de TXT-record ' + record.name + ' nog niet vinden. Hou er rekening mee dat het even (tot 24u) kan duren voor we deze kunnen zien.',
78
- }));
79
+ if (!record.optional) {
80
+ allValid = false;
81
+
82
+ record.errors = new SimpleErrors(new SimpleError({
83
+ code: 'not_found',
84
+ message: '',
85
+ human: 'We konden de TXT-record ' + record.name + ' nog niet vinden. Hou er rekening mee dat het even (tot 24u) kan duren voor we deze kunnen zien.',
86
+ }));
87
+ }
79
88
  }
80
89
  else if (records.length > 1) {
81
90
  record.status = DNSRecordStatus.Failed;
@@ -89,29 +98,20 @@ export async function validateDNSRecords(dnsRecords: DNSRecord[], didRetry = fal
89
98
  else {
90
99
  const val = records[0].join('').trim();
91
100
  if (val === record.value.trim()) {
92
- /* if (records[0].length > 1 && val.length <= 255) {
93
- // Split was not needed and is not supported by SES
94
- record.status = DNSRecordStatus.Failed
95
- allValid = false
96
-
97
- record.errors = new SimpleErrors(new SimpleError({
98
- code: "wrong_value",
99
- message: "",
100
- human: "De waarde komt overeen maar is op één of andere manier opgesplitst in meerdere stukken, terwijl dat niet nodig is. Dit wordt niet ondersteund door onze e-mailprovider. Contacteer ons als je de oorzaak niet kan achterhalen."
101
- }))
102
- } else { */
103
101
  record.status = DNSRecordStatus.Valid;
104
- // }
105
102
  }
106
103
  else {
107
104
  record.status = DNSRecordStatus.Failed;
108
- allValid = false;
109
105
 
110
- record.errors = new SimpleErrors(new SimpleError({
111
- code: 'wrong_value',
112
- message: '',
113
- human: 'Er is een andere waarde ingesteld voor de TXT-record ' + record.name + ', kijk na of je geen typfout hebt gemaakt. Gevonden: ' + records[0].join(''),
114
- }));
106
+ if (!record.optional) {
107
+ allValid = false;
108
+
109
+ record.errors = new SimpleErrors(new SimpleError({
110
+ code: 'wrong_value',
111
+ message: '',
112
+ human: 'Er is een andere waarde ingesteld voor de TXT-record ' + record.name + ', kijk na of je geen typfout hebt gemaakt. Gevonden: ' + records[0].join(''),
113
+ }));
114
+ }
115
115
  }
116
116
  }
117
117
  break;
@@ -121,26 +121,32 @@ export async function validateDNSRecords(dnsRecords: DNSRecord[], didRetry = fal
121
121
  catch (e) {
122
122
  record.status = DNSRecordStatus.Pending;
123
123
 
124
- if (e.code && (e.code == 'ENOTFOUND' || e.code == 'ENODATA')) {
125
- record.errors = new SimpleErrors(new SimpleError({
126
- code: 'not_found',
127
- message: '',
128
- human: 'We konden de record ' + record.name + ' nog niet vinden. Hou er rekening mee dat het even (tot 24u) kan duren voor we deze kunnen zien.',
129
- }));
124
+ if (e.code && (e.code === 'ENOTFOUND' || e.code === 'ENODATA')) {
125
+ if (!record.optional) {
126
+ record.errors = new SimpleErrors(new SimpleError({
127
+ code: 'not_found',
128
+ message: '',
129
+ human: 'We konden de record ' + record.name + ' nog niet vinden. Hou er rekening mee dat het even (tot 24u) kan duren voor we deze kunnen zien.',
130
+ }));
131
+ }
130
132
  }
131
133
  else {
132
134
  console.error(e);
133
-
134
- record.errors = new SimpleErrors(new SimpleError({
135
- code: 'not_found',
136
- message: '',
137
- human: 'Er ging iets mis. Deze record lijkt niet goed ingesteld te zijn.',
138
- }));
135
+ if (!record.optional) {
136
+ record.errors = new SimpleErrors(new SimpleError({
137
+ code: 'not_found',
138
+ message: '',
139
+ human: 'Er ging iets mis. Deze record lijkt niet goed ingesteld te zijn.',
140
+ }));
141
+ }
139
142
  }
140
- allValid = false;
141
143
 
142
- if (record.type !== DNSRecordType.TXT) {
143
- hasAllNonTXT = false;
144
+ if (!record.optional) {
145
+ allValid = false;
146
+
147
+ if (record.type !== DNSRecordType.TXT) {
148
+ hasAllNonTXT = false;
149
+ }
144
150
  }
145
151
  }
146
152
  }
@@ -209,6 +209,7 @@ async function getEmailBuilderForTemplate(organization: Organization | null, opt
209
209
  });
210
210
 
211
211
  if (!template) {
212
+ console.warn('No email template found for ' + options.template.type);
212
213
  return undefined;
213
214
  }
214
215
 
@@ -318,7 +319,7 @@ export async function getEmailBuilder(organization: Organization | null, email:
318
319
 
319
320
  // Override headers
320
321
  recipient.headers = {
321
- 'List-Unsubscribe': `<${unsubscribeUrl}>`,
322
+ 'List-Unsubscribe': STAMHOOFD.domains.defaultBroadcastEmail !== undefined ? '<mailto:unsubscribe+' + unsubscribe.id + '@' + STAMHOOFD.domains.defaultBroadcastEmail![''] + `>, <${unsubscribeUrl}>` : `<${unsubscribeUrl}>`,
322
323
  'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
323
324
  };
324
325
  cleaned.push(recipient);
@@ -0,0 +1,2 @@
1
+ ALTER TABLE `event_notifications`
2
+ ADD COLUMN `acceptedRecordAnswers` json NOT NULL DEFAULT ('{"value": {}, "version": 0}') AFTER `recordAnswers`;
@@ -0,0 +1,2 @@
1
+ INSERT INTO `email_templates` (`id`, `subject`, `groupId`, `webshopId`, `organizationId`, `type`, `text`, `html`, `json`, `updatedAt`, `createdAt`) VALUES
2
+ ('7b28fe12-d06e-49d0-9acf-102a1873253a', 'Kampmelding voor {{eventName}} ({{dateRange}}) werd voorlopig goedgekeurd', NULL, NULL, NULL, 'EventNotificationPartiallyAccepted', '{{greeting}}Goed nieuws! Jullie kampmelding voor {{eventName}} ({{dateRange}}) van {{organizationName}} werd voorlopig goedgekeurd. Deze werd ingediend door {{submitterName}}.\n\n \n \n \n \n Kampmelding bekijken\n \n \n \n \n\nOpmerkingen{{feedbackText}}', '<!DOCTYPE html>\n<html>\n\n<head>\n<meta charset=\"utf-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n<title>Kampmelding voor {{eventName}} ({{dateRange}}) werd voorlopig goedgekeurd</title>\n<style type=\"text/css\">body {\n color: #000716;\n color: var(--color-dark, #000716);\n font-family: -apple-system-body, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n font-size: 12pt;\n line-height: 1.4;\n}\n\np {\n margin: 0;\n padding: 0;\n line-height: 1.4;\n}\n\np.description {\n color: var(--color-gray-4, #5e5e5e);\n}\np.description a, p.description a:link, p.description a:visited, p.description a:active, p.description a:hover {\n text-decoration: underline;\n color: var(--color-gray-4, #5e5e5e);\n}\n\nstrong {\n font-weight: bold;\n}\n\nem {\n font-style: italic;\n}\n\nh1 {\n font-size: 30px;\n font-weight: bold;\n line-height: 1.2;\n margin: 0;\n padding: 0;\n}\n@media (max-width: 350px) {\n h1 {\n font-size: 24px;\n }\n}\n\nh2 {\n font-size: 20px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh3 {\n font-size: 16px;\n line-height: 1.2;\n font-weight: bold;\n margin: 0;\n padding: 0;\n}\n\nh4 {\n line-height: 1.2;\n font-weight: 500;\n margin: 0;\n padding: 0;\n}\n\nol, ul {\n list-style-position: outside;\n padding-left: 30px;\n}\n\nhr {\n height: 1px;\n background: var(--color-border, var(--color-gray-2, #dcdcdc));\n border-radius: 1px;\n padding: 0;\n margin: 20px 0;\n outline: none;\n border: 0;\n}\n\n.button {\n touch-action: inherit;\n user-select: auto;\n cursor: pointer;\n display: inline-block !important;\n line-height: 42px;\n font-size: 16px;\n font-weight: bold;\n}\n.button:active {\n transform: none;\n}\n\nimg {\n max-width: 100%;\n height: auto;\n}\n\na, a:link, a:visited, a:active, a:hover {\n text-decoration: underline;\n color: blue;\n}\n\n.email-data-table {\n width: 100%;\n border-collapse: collapse;\n}\n.email-data-table th, .email-data-table td {\n text-align: left;\n padding: 10px 10px 10px 0;\n border-bottom: 1px solid var(--color-border, var(--color-gray-2, #dcdcdc));\n vertical-align: middle;\n}\n.email-data-table th:last-child, .email-data-table td:last-child {\n text-align: right;\n padding-right: 0;\n}\n.email-data-table thead {\n font-weight: bold;\n}\n.email-data-table thead th {\n font-size: 10pt;\n}\n.email-data-table h4 ~ p {\n padding-top: 3px;\n opacity: 0.8;\n font-size: 11pt;\n}\n\n.email-style-inline-code {\n font-family: monospace;\n white-space: pre-wrap;\n display: inline-block;\n}\n\n.email-style-description-small {\n font-size: 14px;\n line-height: 1.5;\n font-weight: normal;\n color: var(--color-gray-4, #5e5e5e);\n font-variation-settings: \"opsz\" 19;\n}\n\n.email-style-title-list {\n font-size: 16px;\n line-height: 1.3;\n font-weight: 500;\n}\n.email-style-title-list + p {\n padding-top: 3px;\n}\n\n.email-style-title-prefix-list {\n font-size: 11px;\n line-height: 1.5;\n font-weight: bold;\n color: {{primaryColor}};\n text-transform: uppercase;\n margin-bottom: 3px;\n}\n.email-style-title-prefix-list.error {\n color: #f0153d;\n}\n\n.email-style-price-base, .email-style-discount-price, .email-style-discount-old-price, .email-style-price {\n font-size: 15px;\n line-height: 1.4;\n font-weight: 500;\n font-variant-numeric: tabular-nums;\n}\n.email-style-price-base.disabled, .disabled.email-style-discount-price, .disabled.email-style-discount-old-price, .disabled.email-style-price {\n opacity: 0.6;\n}\n.email-style-price-base.negative, .negative.email-style-discount-price, .negative.email-style-discount-old-price, .negative.email-style-price {\n color: #f0153d;\n}\n\n.email-style-price {\n font-weight: bold;\n color: {{primaryColor}};\n}\n\n.email-style-discount-old-price {\n text-decoration: line-through;\n color: var(--color-gray-4, #5e5e5e);\n}\n\n.email-style-discount-price {\n font-weight: bold;\n color: #ff4747;\n margin-left: 5px;\n}\n\n.pre-wrap {\n white-space: pre-wrap;\n} hr {height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;} .button.primary { margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity; } .button.primary:active { transform: scale(0.95, 0.95); } .inline-link, .inline-link:link, .inline-link:visited, .inline-link:active, .inline-link:hover { margin: 0; text-decoration: underline; font-size: inherit; font-weight: inherit; color: inherit; touch-action: manipulation; } .inline-link:active { opacity: 0.5; } .description { color: #5e5e5e; } </style>\n</head>\n\n<body>\n<p style=\"margin: 0; padding: 0; line-height: 1.4;\">{{greeting}}</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><p style=\"margin: 0; padding: 0; line-height: 1.4;\">Goed nieuws! Jullie kampmelding voor <strong>{{eventName}}</strong> ({{dateRange}}) van <strong>{{organizationName}}</strong> werd voorlopig goedgekeurd. Deze werd ingediend door {{submitterName}}.</p><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartButton\" data-id=\"reviewUrl\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" style=\"margin: 5px 0;\">\n<tbody><tr>\n <td>\n <table cellspacing=\"0\" cellpadding=\"0\">\n <tbody><tr>\n <td style=\"border-radius: 7px;\" bgcolor=\"{{primaryColor}}\">\n <a class=\"button primary\" href=\"{{reviewUrl}}\" target=\"\" style=\"margin: 0; text-decoration: none; font-size: 16px; font-weight: bold; color: {{primaryColorContrast}}; padding: 0 27px; line-height: 42px; background: {{primaryColor}}; text-align: center; border-radius: 7px; touch-action: manipulation; display: inline-block; transition: 0.2s transform, 0.2s opacity;\">Kampmelding bekijken</a>\n </td>\n </tr>\n </tbody></table>\n </td>\n</tr>\n</tbody></table></div><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><hr style=\"height: 2px;background: #e7e7e7; border-radius: 1px; padding: 0; margin: 20px 0; outline: none; border: 0;\"><h2 style=\"margin: 0; padding: 0;\">Opmerkingen</h2><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p><div data-type=\"smartVariableBlock\" data-id=\"feedbackText\">{{feedbackText}}</div><p style=\"margin: 0; padding: 0; line-height: 1.4;\"><br></p>\n</body>\n\n</html>', '{\"value\": {\"type\": \"doc\", \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"smartVariable\", \"attrs\": {\"id\": \"greeting\"}}]}, {\"type\": \"paragraph\"}, {\"type\": \"paragraph\", \"content\": [{\"text\": \"Goed nieuws! Jullie kampmelding voor \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"eventName\"}, \"marks\": [{\"type\": \"bold\"}]}, {\"text\": \" (\", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"dateRange\"}}, {\"text\": \") van \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"organizationName\"}, \"marks\": [{\"type\": \"bold\"}]}, {\"text\": \" werd voorlopig goedgekeurd. Deze werd ingediend door \", \"type\": \"text\"}, {\"type\": \"smartVariable\", \"attrs\": {\"id\": \"submitterName\"}}, {\"text\": \".\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartButton\", \"attrs\": {\"id\": \"reviewUrl\"}, \"content\": [{\"text\": \"Kampmelding bekijken\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"horizontalRule\"}, {\"type\": \"heading\", \"attrs\": {\"level\": 2}, \"content\": [{\"text\": \"Opmerkingen\", \"type\": \"text\"}]}, {\"type\": \"paragraph\"}, {\"type\": \"smartVariableBlock\", \"attrs\": {\"id\": \"feedbackText\"}}, {\"type\": \"paragraph\"}]}, \"version\": 367}', '2025-03-19 14:16:02', '2025-03-19 14:16:02');
@@ -0,0 +1,3 @@
1
+ ALTER TABLE `audit_logs`
2
+ CHANGE `externalId` `externalId` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
3
+ CHANGE `objectId` `objectId` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL;