@camphoric/camphoric-event-import-tools 0.0.0-stage โ†’ 1.2.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 (59) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/CamphoricEventCreator.ts +551 -0
  3. package/CamphoricFetcher.ts +156 -0
  4. package/README.md +59 -2
  5. package/bulkRegistrations.ts +271 -0
  6. package/dist/CamphoricEventCreator.d.ts +80 -0
  7. package/dist/CamphoricEventCreator.d.ts.map +1 -0
  8. package/dist/CamphoricEventCreator.js +380 -0
  9. package/dist/CamphoricEventCreator.js.map +1 -0
  10. package/dist/CamphoricFetcher.d.ts +28 -0
  11. package/dist/CamphoricFetcher.d.ts.map +1 -0
  12. package/dist/CamphoricFetcher.js +116 -0
  13. package/dist/CamphoricFetcher.js.map +1 -0
  14. package/dist/bulkRegistrations.d.ts +59 -0
  15. package/dist/bulkRegistrations.d.ts.map +1 -0
  16. package/dist/bulkRegistrations.js +223 -0
  17. package/dist/bulkRegistrations.js.map +1 -0
  18. package/dist/e2e.d.ts +55 -0
  19. package/dist/e2e.d.ts.map +1 -0
  20. package/dist/e2e.js +99 -0
  21. package/dist/e2e.js.map +1 -0
  22. package/dist/e2eImport.d.ts +16 -0
  23. package/dist/e2eImport.d.ts.map +1 -0
  24. package/dist/e2eImport.js +38 -0
  25. package/dist/e2eImport.js.map +1 -0
  26. package/dist/e2eRegistrationForm.d.ts +71 -0
  27. package/dist/e2eRegistrationForm.d.ts.map +1 -0
  28. package/dist/e2eRegistrationForm.js +167 -0
  29. package/dist/e2eRegistrationForm.js.map +1 -0
  30. package/dist/e2eServer.d.ts +22 -0
  31. package/dist/e2eServer.d.ts.map +1 -0
  32. package/dist/e2eServer.js +93 -0
  33. package/dist/e2eServer.js.map +1 -0
  34. package/dist/getAuthInfo.d.ts +2 -0
  35. package/dist/getAuthInfo.d.ts.map +1 -0
  36. package/dist/getAuthInfo.js +26 -0
  37. package/dist/getAuthInfo.js.map +1 -0
  38. package/dist/organizations.d.ts +29 -0
  39. package/dist/organizations.d.ts.map +1 -0
  40. package/dist/organizations.js +107 -0
  41. package/dist/organizations.js.map +1 -0
  42. package/dist/types.d.ts +98 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +8 -0
  45. package/dist/types.js.map +1 -0
  46. package/dist/utils.d.ts +7 -0
  47. package/dist/utils.d.ts.map +1 -0
  48. package/dist/utils.js +27 -0
  49. package/dist/utils.js.map +1 -0
  50. package/e2e-compose.yml +40 -0
  51. package/e2e.ts +111 -0
  52. package/e2eImport.ts +61 -0
  53. package/e2eRegistrationForm.ts +190 -0
  54. package/e2eServer.ts +114 -0
  55. package/getAuthInfo.ts +31 -0
  56. package/organizations.ts +144 -0
  57. package/package.json +102 -4
  58. package/types.ts +103 -0
  59. package/utils.ts +29 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ ## [1.2.0](https://github.com/camphoric/camphoric/compare/camphoric-event-import-tools-v1.1.0...camphoric-event-import-tools-v1.2.0) (2026-10-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * **data:** count a camper's lodging levels in the e2e page object ([9f7ac3b](https://github.com/camphoric/camphoric/commit/9f7ac3b06c5647ea1b5796756034eb4694bce01d)), closes [#780](https://github.com/camphoric/camphoric/issues/780)
9
+
10
+ ## [1.1.0](https://github.com/camphoric/camphoric/compare/camphoric-event-import-tools-v1.0.0...camphoric-event-import-tools-v1.1.0) (2026-10-17)
11
+
12
+
13
+ ### Features
14
+
15
+ * **data:** add e2e scaffolding for testing an event's registration form ([e98f014](https://github.com/camphoric/camphoric/commit/e98f01486f51fa83773c2840bd32734ed3e61e67))
16
+ * **data:** move the event creator and organization loader into import-tools ([c6e8468](https://github.com/camphoric/camphoric/commit/c6e8468a6b2326885a74e24eb036e3c57aa67c1e))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **data:** keep reset-data-and-test from importing alongside docker compose up ([7543433](https://github.com/camphoric/camphoric/commit/754343397c8cfdb94f473b0ebd98b7262db61534))
22
+ * **data:** open an e2e event's registration before its sample registrations ([7543433](https://github.com/camphoric/camphoric/commit/754343397c8cfdb94f473b0ebd98b7262db61534))
23
+ * **data:** report an event module's validation errors when it has no event ([c6e8468](https://github.com/camphoric/camphoric/commit/c6e8468a6b2326885a74e24eb036e3c57aa67c1e))
24
+ * **data:** stop a failed import request from ending the process ([7543433](https://github.com/camphoric/camphoric/commit/754343397c8cfdb94f473b0ebd98b7262db61534))
25
+
26
+ ## 1.0.0 (2026-10-17)
27
+
28
+
29
+ ### Features
30
+
31
+ * **data:** let each event declare the organization it belongs to ([70853bd](https://github.com/camphoric/camphoric/commit/70853bd5223b0134fd7e259a241c2dc05d22b6c1))
32
+ * **data:** publish the event import spec and tools to GitHub Packages ([d26b182](https://github.com/camphoric/camphoric/commit/d26b182d5fe44cc31dbd1747572a267439053574))
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * **data:** keep the event-import packages' versions independent ([fd96655](https://github.com/camphoric/camphoric/commit/fd96655bf7b707b7771c964a79781879e2a0c4af))
@@ -0,0 +1,551 @@
1
+ /**
2
+ * Import test event data
3
+ *
4
+ * Sample use
5
+ *
6
+ * ```
7
+ * const event = new CamphoricEventCreator({
8
+ * data,
9
+ * sampleRegGenerator
10
+ * overrides,
11
+ * url,
12
+ * });
13
+ *
14
+ * event.send();
15
+ * ```
16
+ *
17
+ * Arguments
18
+ * ---------
19
+ *
20
+ * data: an event import object (the spec's EventImport), checked first with
21
+ * `validate`, the spec's validateEventImport. Every field of `data.event` is sent to the
22
+ * events API as-is, including optional ones such as
23
+ * `registration_error_messages` (custom validation messages for the
24
+ * registration form).
25
+ *
26
+ * sampleRegGenerator: a SampleRegGenerator, called with
27
+ * - fetch: a fetch function
28
+ * - results: the data generated so far of the event creation
29
+ * - log: a log function
30
+ * This function should return an array of objects which can be POSTed to the
31
+ * register endpoint.
32
+ *
33
+ *
34
+ */
35
+
36
+ import { oraPromise } from 'ora';
37
+ import cliProgress from 'cli-progress';
38
+ import lodash from 'lodash';
39
+
40
+ import Fetcher from './CamphoricFetcher.ts';
41
+ import { Organizations, type Organization } from './organizations.ts';
42
+ import type {
43
+ ImportData,
44
+ ImportResults,
45
+ LodgingNode,
46
+ Override,
47
+ SampleRegGenerator,
48
+ SampleRegistration,
49
+ Saved,
50
+ SavedResults,
51
+ } from './types.ts';
52
+ import { formatDate, formatDateTime } from './utils.ts';
53
+
54
+ export interface CamphoricEventCreatorOptions {
55
+ data: ImportData;
56
+ // Checks the data before anything is sent, returning its problems (none
57
+ // when it's valid): pass the spec's validateEventImport.
58
+ validate: (data: unknown) => unknown[];
59
+ sampleRegGenerator?: SampleRegGenerator;
60
+ overrides?: Override[];
61
+ // The organizations already loaded; fetched from the server when not given
62
+ organizations?: Organizations;
63
+ url?: string;
64
+ }
65
+
66
+ // A sample registration, with the server's answer to its last step
67
+ type PostedRegistration = SampleRegistration & { response: Record<string, unknown> };
68
+
69
+ /**
70
+ * Requests started ahead of the Promise.all that collects them, pausing
71
+ * between batches so the server keeps up. Each is handled as it starts, so
72
+ * one that fails during a pause isn't an unhandled rejection (which ends the
73
+ * process, skipping the other events and any cleanup); once one has failed,
74
+ * `failed` says to start no more. Promise.all still rejects with its error.
75
+ */
76
+ export function requestBatch() {
77
+ let failed = false;
78
+ return {
79
+ get failed() { return failed; },
80
+ start<T>(request: Promise<T>): Promise<T> {
81
+ request.catch(() => { failed = true; });
82
+ return request;
83
+ },
84
+ };
85
+ }
86
+
87
+ export default class CamphoricEventCreator extends Fetcher {
88
+ declare data: ImportData;
89
+ sampleRegGenerator?: SampleRegGenerator;
90
+ overrides: Override[];
91
+ results: ImportResults;
92
+ organization?: Promise<Organization | undefined>;
93
+
94
+ constructor({ data, validate, organizations, sampleRegGenerator, overrides = [], url }: CamphoricEventCreatorOptions) {
95
+ super(url);
96
+
97
+ this.data = data;
98
+
99
+ const errors = validate(data);
100
+
101
+ if (errors.length) {
102
+ throw this.error(
103
+ 'event data object did not pass JSON schema validation',
104
+ ...errors,
105
+ );
106
+ }
107
+
108
+ this.setOrganizations(organizations, data.organization);
109
+
110
+ if (sampleRegGenerator) {
111
+ if (typeof sampleRegGenerator !== 'function') {
112
+ throw new Error('sample registration generator passed should be a function');
113
+ }
114
+
115
+ this.sampleRegGenerator = sampleRegGenerator;
116
+ }
117
+
118
+ if (!Array.isArray(overrides)) {
119
+ throw new Error('overrides passed should be an array');
120
+ }
121
+
122
+ if (overrides.find(e => typeof e !== 'function')) {
123
+ throw new Error('all items in overrides be functions');
124
+ }
125
+
126
+ this.overrides = overrides;
127
+
128
+ this.results = {};
129
+ }
130
+
131
+ pathFormat(path: string): string {
132
+ if (path.charAt(path.length - 1) !== '/') {
133
+ path += '/';
134
+ }
135
+
136
+ return path;
137
+ }
138
+
139
+ // The saved event, which every step after loadEvent() needs
140
+ savedEvent(): Saved {
141
+ if (!this.results.event) {
142
+ throw this.error('the event has not been saved yet');
143
+ }
144
+
145
+ return this.results.event;
146
+ }
147
+
148
+ // What has been saved, once the event has
149
+ savedResults(): SavedResults {
150
+ this.savedEvent();
151
+
152
+ return this.results as SavedResults;
153
+ }
154
+
155
+ async deleteAll(path: string) {
156
+ const event = this.savedEvent();
157
+ const existing: Saved[] = await this.fetch('GET', path)
158
+ .then((all: Saved[]) => all.filter(i => i.event === event.id));
159
+
160
+ await Promise.all(existing.map(
161
+ l => this.fetch('DELETE', `${this.pathFormat(path)}${l.id}/`)
162
+ ));
163
+ }
164
+
165
+ async loadEvent() {
166
+ const org = await this.organization;
167
+ if (!org) {
168
+ throw this.error(`organization '${this.data.organization}' not found`);
169
+ }
170
+
171
+ const events: Saved[] = await this.fetch('GET', '/api/events/');
172
+
173
+ const existingEvent = events.find(event => event.name === this.data.event.name);
174
+
175
+ const emailAccounts: Saved[] = await this.fetch('GET', '/api/emailaccounts/');
176
+ const email = emailAccounts.find(e => e.organization === org.id);
177
+
178
+ // The confirmation email is the event's email template, updated below.
179
+ const {
180
+ confirmation_email_subject: subject,
181
+ confirmation_email_template: body,
182
+ ...eventData
183
+ } = this.data.event;
184
+ const event: Record<string, unknown> = {
185
+ organization: org.id,
186
+ ...eventData,
187
+ // The event's dates are days; its registration window is instants.
188
+ start: formatDate(eventData.start),
189
+ end: formatDate(eventData.end),
190
+ registration_start: formatDateTime(eventData.registration_start),
191
+ registration_end: formatDateTime(eventData.registration_end),
192
+ };
193
+
194
+ if (existingEvent) {
195
+ event.id = existingEvent.id;
196
+ }
197
+
198
+ if (email) {
199
+ event.email_account = email.id;
200
+ event.confirmation_email_from =
201
+ event.confirmation_email_from || email.account;
202
+ }
203
+
204
+ const eventResponse: Saved = await this.fetch(
205
+ existingEvent ? 'PUT' : 'POST',
206
+ `/api/events/${existingEvent ? event.id : ''}`,
207
+ event,
208
+ );
209
+
210
+ await this.loadEmailTemplate(eventResponse.confirmation_template, subject, body);
211
+
212
+ this.results.event = eventResponse;
213
+ return eventResponse;
214
+ }
215
+
216
+ // An event's confirmation and a registration type's invitation are email
217
+ // templates, created with them (SPEC DR-45); set the text the data gives.
218
+ async loadEmailTemplate(templateId: unknown, subject?: string, body?: string) {
219
+ if (!templateId || (subject === undefined && body === undefined)) return;
220
+ await this.fetch('PATCH', `/api/emailtemplates/${templateId}/`, {
221
+ ...(subject !== undefined ? { subject } : {}),
222
+ ...(body !== undefined ? { body } : {}),
223
+ });
224
+ }
225
+
226
+ async loadLodgings() {
227
+ const event = this.savedEvent();
228
+ const progBar = new cliProgress.SingleBar(
229
+ {
230
+ format: 'importing lodging [{bar}] {percentage}% | {value}/{total}',
231
+ clearOnComplete: true,
232
+ },
233
+ cliProgress.Presets.shades_classic
234
+ );
235
+
236
+ // delete existing lodgings associated with this event, so we start clean
237
+ await this.deleteAll('/api/lodgings/');
238
+
239
+ if (!this.data.lodgings) {
240
+ return;
241
+ }
242
+
243
+ const lodgingLookup = this.data.lodgings;
244
+ const lodgingEntries = Object.entries(lodgingLookup);
245
+ let completed = 0;
246
+ progBar.start(lodgingEntries.length, 0);
247
+
248
+ const createLodging = async (key: string, lodging: LodgingNode): Promise<unknown> => {
249
+ let data, createdLodging: Saved | undefined;
250
+ try {
251
+ data = {
252
+ event: event.id,
253
+ parent: lodging.parentKey ? lodgingLookup[lodging.parentKey].id : null,
254
+ name: lodging.name,
255
+ children_title: lodging.children_title,
256
+ visible: lodging.visible,
257
+ capacity: lodging.capacity || 0,
258
+ reserved: lodging.reserved || 0,
259
+ sharing_multiplier: lodging.sharing_multiplier || 1,
260
+ notes: lodging.notes || '',
261
+ };
262
+ const saved: Saved = await this.fetch('POST', '/api/lodgings/', data);
263
+ createdLodging = saved;
264
+
265
+ completed += 1;
266
+ progBar.update(completed);
267
+ this.logVerbose(`created lodging ${completed}/${lodgingEntries.length}`);
268
+
269
+ lodgingLookup[key].id = saved.id;
270
+ } catch (e) {
271
+ throw this.error(e, {type: 'lodgingDataAttempted', key, data, createdLodging});
272
+ }
273
+
274
+ // batch requests so that it doesn't time out
275
+ const promises = [];
276
+ const requests = requestBatch();
277
+ const all = lodgingEntries.filter(([, l]) => l.parentKey === key);
278
+ for (let i = 0; i < all.length && !requests.failed; ++i) {
279
+ promises.push(requests.start(createLodging(...all[i])));
280
+
281
+ if (promises.length % 10 === 0) await this.wait(5000);
282
+ }
283
+
284
+ return Promise.all(promises);
285
+ };
286
+
287
+ // recursively add all lodgings starting with root
288
+ await createLodging('root', lodgingLookup.root);
289
+ progBar.stop();
290
+
291
+ this.results.lodging = lodgingLookup;
292
+ }
293
+
294
+ async loadReports() {
295
+ const event = this.savedEvent();
296
+ this.results.reports = await Promise.all(this.data.reports.map(
297
+ report => this.fetch('POST', '/api/reports/', {
298
+ event: event.id,
299
+ // data/ reports use the browser's variable bundle unless they say otherwise.
300
+ variables_source: 'client',
301
+ ...report,
302
+ })
303
+ ));
304
+ }
305
+
306
+ async loadCustomChargeTypes() {
307
+ const { custom_charge_types } = this.data;
308
+
309
+ if (!custom_charge_types || !custom_charge_types.length) return;
310
+
311
+ const event = this.savedEvent();
312
+
313
+ const response: Saved[] = await this.fetch('GET', '/api/customchargetypes/');
314
+
315
+ const existing = response.filter(ctype => ctype.event === event.id);
316
+
317
+ await Promise.all(
318
+ custom_charge_types.map(
319
+ async (customChargeType) => {
320
+ const exists = existing.find(r => r.name === customChargeType.name);
321
+
322
+ return this.fetch(
323
+ exists ? 'PUT' : 'POST',
324
+ `/api/customchargetypes${exists ? `/${exists.id}` : ''}/`,
325
+ {
326
+ ...customChargeType,
327
+ ...(exists ? { id: exists.id } : {}),
328
+ event: event.id,
329
+ },
330
+ );
331
+ }
332
+ )
333
+ );
334
+ }
335
+
336
+ async loadRegTypes() {
337
+ const event = this.savedEvent();
338
+
339
+ const response: Saved[] = await this.fetch('GET', '/api/registrationtypes/');
340
+
341
+ const existingRegTypes = response.filter(regType => regType.event === event.id);
342
+
343
+ await Promise.all(
344
+ this.data.registration_types.map(
345
+ async (regType) => {
346
+ const exists = existingRegTypes.find(r => r.name === regType.name);
347
+
348
+ const {
349
+ invitation_email_subject: subject,
350
+ invitation_email_template: body,
351
+ ...typeData
352
+ } = regType;
353
+
354
+ const saved: Saved = await this.fetch(
355
+ exists ? 'PUT' : 'POST',
356
+ `/api/registrationtypes${exists ? `/${exists.id}` : ''}/`,
357
+ {
358
+ ...typeData,
359
+ ...(exists ? { id: exists.id } : {}),
360
+ event: event.id,
361
+ },
362
+ );
363
+ await this.loadEmailTemplate(saved.invitation_template, subject, body);
364
+ return saved;
365
+ }
366
+ )
367
+ );
368
+ }
369
+
370
+ async loadTestRegs() {
371
+ if (!this.sampleRegGenerator) return [];
372
+
373
+ const token = await this.getAuthToken();
374
+ const registrations = await this.sampleRegGenerator.bind(this)(
375
+ this.fetch.bind(this),
376
+ this.savedResults(),
377
+ this.log.bind(this),
378
+ );
379
+
380
+ const event = this.savedEvent();
381
+
382
+ const postReg = async (testData: SampleRegistration): Promise<PostedRegistration> => {
383
+ const res = await fetch(`${this.urlBase}/api/events/${event.id}/register`, {
384
+ method: 'POST',
385
+ headers: {
386
+ 'Content-Type': 'application/json',
387
+ 'X-CSRFToken': token,
388
+ },
389
+ body: JSON.stringify({
390
+ step: 'registration',
391
+ formData: testData.formData,
392
+ pricingResults: testData.pricingResults,
393
+ }),
394
+ });
395
+
396
+ const text = await res.text();
397
+
398
+ // A rejected test registration is a real import failure (a broken schema or
399
+ // pricing logic), so fail loudly instead of logging and exiting 0.
400
+ let json;
401
+ try {
402
+ json = JSON.parse(text);
403
+ } catch (e) {
404
+ throw this.error(
405
+ `[reg step] test registration returned non-JSON (${res.status}), see Django logs`,
406
+ { status: res.status, body: text },
407
+ );
408
+ }
409
+
410
+ if (!res.ok) {
411
+ throw this.error(
412
+ `[reg step] test registration rejected (${res.status})`,
413
+ { type: 'testRegistration', response: json, sent: testData.formData },
414
+ );
415
+ }
416
+
417
+ return {
418
+ ...testData,
419
+ response: json || {},
420
+ };
421
+ };
422
+
423
+ const postPayment = async (testData: PostedRegistration): Promise<PostedRegistration> => {
424
+ // Test registrations pay by check: a PayPal payment needs a payer to
425
+ // approve it in PayPal's window (SPEC ยง7.2). The server works out the
426
+ // amount from the payment option (the event's default when none is given).
427
+ const paymentOption = lodash.get(testData, 'paymentData.paymentOption');
428
+
429
+ const body = {
430
+ step: 'payment',
431
+ registrationUUID: testData.response.registrationUUID,
432
+ paymentType: 'Check',
433
+ ...(paymentOption ? { paymentOption } : {}),
434
+ };
435
+
436
+ const res = await fetch(`${this.urlBase}/api/events/${event.id}/register`, {
437
+ method: 'POST',
438
+ headers: {
439
+ 'Content-Type': 'application/json',
440
+ 'X-CSRFToken': token,
441
+ },
442
+ body: JSON.stringify(body),
443
+ });
444
+
445
+ const text = await res.text();
446
+
447
+ let json;
448
+ try {
449
+ json = JSON.parse(text);
450
+ } catch (e) {
451
+ throw this.error(
452
+ `[pay step] test payment returned non-JSON (${res.status}), see Django logs`,
453
+ { status: res.status, body: text },
454
+ );
455
+ }
456
+
457
+ if (!res.ok) {
458
+ throw this.error(
459
+ `[pay step] test payment rejected (${res.status})`,
460
+ { type: 'testPayment', response: json, sent: body },
461
+ );
462
+ }
463
+
464
+ return {
465
+ ...testData,
466
+ response: json || {},
467
+ };
468
+
469
+ };
470
+
471
+ if (!Array.isArray(registrations)) {
472
+ throw new Error('sample reg generator function returned a non-array');
473
+ }
474
+
475
+ // get first step responses
476
+ const step1 = [];
477
+ const registering = requestBatch();
478
+ for (let i = 0; i < registrations.length && !registering.failed; ++i) {
479
+ step1.push(registering.start(postReg(registrations[i])));
480
+
481
+ if (i % 10 === 0) await this.wait(1500);
482
+ }
483
+ const responses = await Promise.all(step1);
484
+
485
+ // process payment step
486
+ const step2 = [];
487
+ const paying = requestBatch();
488
+ for (let i = 0; i < responses.length && !paying.failed; ++i) {
489
+ step2.push(paying.start(postPayment(responses[i])));
490
+
491
+ if (i % 10 === 0) await this.wait(1500);
492
+ }
493
+
494
+ return Promise.all(step2);
495
+ }
496
+
497
+ async create() {
498
+ this.log(`๐ŸŸข Starting Import of ${this.data.event.name}!`);
499
+
500
+ const imp = (label: string, p: Promise<unknown>) => oraPromise(
501
+ p,
502
+ {
503
+ text: `[${this.data.event.name}] import ${label}`,
504
+ spinner: 'clock',
505
+ }
506
+ );
507
+
508
+ await imp('organization', Promise.resolve(this.organization));
509
+ await imp('event', this.loadEvent());
510
+ await imp('custom charge types', this.loadCustomChargeTypes());
511
+ await imp('reports', this.loadReports());
512
+ await imp('registration types', this.loadRegTypes());
513
+
514
+ await this.loadLodgings();
515
+ await imp('lodging', Promise.resolve());
516
+
517
+ // Run the overrides in series in case order matters
518
+ for (let i = 0; i < this.overrides.length; i++) {
519
+ const fn = this.overrides[i].bind(this);
520
+
521
+ await imp(`override[${i}]`, fn(this.fetch.bind(this), this.savedResults(), this.log.bind(this)));
522
+ }
523
+
524
+ // SKIP_TEST_REGS=1 skips the sample registrations (e.g. where a sample would
525
+ // need a live PayPal sandbox); everything else still imports.
526
+ if (this.sampleRegGenerator && process.env.SKIP_TEST_REGS !== '1') {
527
+ await imp('test registrations', this.loadTestRegs());
528
+ }
529
+
530
+ this.log(`๐ŸŽ‰ Finished Importing ${this.data.event.name}!`);
531
+
532
+ return true;
533
+ }
534
+
535
+ setOrganizations = async (organizations: Organizations | undefined, organization: string | number) => {
536
+ if (!(organizations instanceof Organizations)) {
537
+ organizations = new Organizations(
538
+ await this.fetch('GET', '/api/organizations/')
539
+ );
540
+ }
541
+
542
+ // get organization
543
+ this.organization = new Promise(
544
+ r => r(organizations.byId[organization] || organizations.byName[organization])
545
+ );
546
+
547
+ if (!await this.organization) {
548
+ throw new Error(`organization '${organization}' not found`);
549
+ }
550
+ };
551
+ }