@stamhoofd/backend 2.96.1 → 2.96.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.96.1",
3
+ "version": "2.96.3",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -45,14 +45,14 @@
45
45
  "@simonbackx/simple-encoding": "2.22.0",
46
46
  "@simonbackx/simple-endpoints": "1.20.1",
47
47
  "@simonbackx/simple-logging": "^1.0.1",
48
- "@stamhoofd/backend-i18n": "2.96.1",
49
- "@stamhoofd/backend-middleware": "2.96.1",
50
- "@stamhoofd/email": "2.96.1",
51
- "@stamhoofd/models": "2.96.1",
52
- "@stamhoofd/queues": "2.96.1",
53
- "@stamhoofd/sql": "2.96.1",
54
- "@stamhoofd/structures": "2.96.1",
55
- "@stamhoofd/utility": "2.96.1",
48
+ "@stamhoofd/backend-i18n": "2.96.3",
49
+ "@stamhoofd/backend-middleware": "2.96.3",
50
+ "@stamhoofd/email": "2.96.3",
51
+ "@stamhoofd/models": "2.96.3",
52
+ "@stamhoofd/queues": "2.96.3",
53
+ "@stamhoofd/sql": "2.96.3",
54
+ "@stamhoofd/structures": "2.96.3",
55
+ "@stamhoofd/utility": "2.96.3",
56
56
  "archiver": "^7.0.1",
57
57
  "axios": "^1.8.2",
58
58
  "cookie": "^0.7.0",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "649831e89207ca962da599f6a9e8e69e1f8abd58"
73
+ "gitHead": "9da200d4958b6959f604def8861ac00595a668cf"
74
74
  }
@@ -21,7 +21,7 @@ async function balanceEmails() {
21
21
  return;
22
22
  }
23
23
 
24
- if ((new Date().getHours() > 10 || new Date().getHours() < 6) && STAMHOOFD.environment !== 'development') {
24
+ if ((new Date().getHours() > 18 || new Date().getHours() < 6) && STAMHOOFD.environment !== 'development') {
25
25
  return;
26
26
  }
27
27
 
@@ -1,8 +1,8 @@
1
1
  import { Decoder } from '@simonbackx/simple-encoding';
2
2
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
3
3
  import { SimpleError } from '@simonbackx/simple-errors';
4
- import { Group, Member, Platform } from '@stamhoofd/models';
5
- import { SQL, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
4
+ import { Group, Member, Platform, Registration } from '@stamhoofd/models';
5
+ import { SQL, SQLExpression, SQLSelect, SQLSortDefinitions, applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
6
6
  import { CountFilteredRequest, GroupType, LimitedFilteredRequest, PaginatedResponse, PermissionLevel, StamhoofdFilter, assertSort } from '@stamhoofd/structures';
7
7
 
8
8
  import { SQLResultNamespacedRow } from '@simonbackx/simple-database';
@@ -11,7 +11,7 @@ import { RegistrationWithMemberBlob } from '@stamhoofd/structures/dist/src/membe
11
11
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
12
12
  import { Context } from '../../../helpers/Context';
13
13
  import { LimitedFilteredRequestHelper } from '../../../helpers/LimitedFilteredRequestHelper';
14
- import { registrationFilterCompilers } from '../../../sql-filters/registrations';
14
+ import { groupJoin, registrationFilterCompilers } from '../../../sql-filters/registrations';
15
15
  import { registrationSorters } from '../../../sql-sorters/registrations';
16
16
  import { GetMembersEndpoint } from '../members/GetMembersEndpoint';
17
17
  import { validateGroupFilter } from '../members/helpers/validateGroupFilter';
@@ -40,6 +40,29 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
40
40
  return [false];
41
41
  }
42
42
 
43
+ static selectRegistrationWithGroup(...columns: (SQLExpression | string)[]): SQLSelect<Registration & { group: Group }> {
44
+ const transformer = (row: SQLResultNamespacedRow): Registration & { group: Group } => {
45
+ const d = Registration.fromRow(row[Registration.table]);
46
+
47
+ if (!d) {
48
+ console.error('Could not transform row', row, 'into model', Registration.table, 'check if the primary key is returned in the query');
49
+ throw new Error('Missing data for model ' + Registration.table);
50
+ }
51
+
52
+ const g = Group.fromRow(row[Group.table]);
53
+ if (!g) {
54
+ console.error('Could not transform row', row, 'into model', Group.table, 'check if the primary key is returned in the query');
55
+ throw new Error('Missing data for model ' + Group.table);
56
+ }
57
+
58
+ return d.setRelation(Registration.group, g);
59
+ };
60
+
61
+ const select = new SQLSelect(transformer, ...(columns.length === 0 ? [SQL.wildcard(), SQL.wildcard(Group.table)] : columns));
62
+ select.join(groupJoin);
63
+ return select.from(SQL.table(Registration.table));
64
+ }
65
+
43
66
  static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest, permissionLevel: PermissionLevel = PermissionLevel.Read) {
44
67
  const organization = Context.organization;
45
68
  let scopeFilter: StamhoofdFilter | undefined = undefined;
@@ -115,15 +138,8 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
115
138
  }
116
139
  }
117
140
 
118
- const query = SQL
119
- .select(
120
- SQL.column('registrations', 'id'),
121
- SQL.column('registrations', 'memberId'),
122
- )
141
+ const query = this.selectRegistrationWithGroup()
123
142
  .setMaxExecutionTime(15 * 1000)
124
- .from(
125
- SQL.table('registrations'),
126
- )
127
143
  .where('registeredAt', '!=', null);
128
144
 
129
145
  if (scopeFilter) {
@@ -161,7 +177,9 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
161
177
 
162
178
  static async buildData(requestQuery: LimitedFilteredRequest, permissionLevel = PermissionLevel.Read) {
163
179
  const query = await GetRegistrationsEndpoint.buildQuery(requestQuery, permissionLevel);
164
- let data: SQLResultNamespacedRow[];
180
+ let data: (Registration & {
181
+ group: Group;
182
+ })[];
165
183
 
166
184
  try {
167
185
  data = await query.fetch();
@@ -177,14 +195,13 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
177
195
  throw error;
178
196
  }
179
197
 
180
- const registrationData = data.map((r) => {
181
- if (typeof r.registrations.memberId === 'string' && typeof r.registrations.id === 'string') {
182
- return { memberId: r.registrations.memberId, id: r.registrations.id };
198
+ for (const registration of data) {
199
+ if (!await Context.auth.canAccessRegistration(registration, permissionLevel)) {
200
+ throw Context.auth.error();
183
201
  }
184
- throw new Error('Expected string');
185
- });
202
+ }
186
203
 
187
- const members = await Member.getBlobByIds(...registrationData.map(r => r.memberId));
204
+ const members = await Member.getBlobByIds(...data.map(r => r.memberId));
188
205
 
189
206
  for (const member of members) {
190
207
  if (!await Context.auth.canAccessMember(member, permissionLevel)) {
@@ -192,7 +209,7 @@ export class GetRegistrationsEndpoint extends Endpoint<Params, Query, Body, Resp
192
209
  }
193
210
  }
194
211
 
195
- const registrationsBlob = await AuthenticatedStructures.registrationsBlob(registrationData, members);
212
+ const registrationsBlob = await AuthenticatedStructures.registrationsBlob(data, members);
196
213
 
197
214
  const next = LimitedFilteredRequestHelper.fixInfiniteLoadingLoop({
198
215
  request: requestQuery,
@@ -456,6 +456,9 @@ export class AuthenticatedStructures {
456
456
 
457
457
  const memberBlobs: MemberWithRegistrationsBlob[] = [];
458
458
  for (const member of members) {
459
+ const filtered: (Registration & {
460
+ group: Group;
461
+ })[] = [];
459
462
  for (const registration of member.registrations) {
460
463
  if (includeContextOrganization || registration.organizationId !== Context.auth.organization?.id) {
461
464
  const found = organizations.get(registration.id);
@@ -464,8 +467,11 @@ export class AuthenticatedStructures {
464
467
  organizations.set(organization.id, organization);
465
468
  }
466
469
  }
470
+ if (organizations.get(registration.organizationId)?.active || (Context.auth.organization && Context.auth.organization.active && registration.organizationId === Context.auth.organization.id) || await Context.auth.hasFullAccess(registration.organizationId)) {
471
+ filtered.push(registration);
472
+ }
467
473
  }
468
- member.registrations = member.registrations.filter(r => (Context.auth.organization && Context.auth.organization.active && r.organizationId === Context.auth.organization.id) || (organizations.get(r.organizationId)?.active ?? false));
474
+ member.registrations = filtered;
469
475
  const balancesPermission = await Context.auth.hasFinancialMemberAccess(member, PermissionLevel.Read, Context.organization?.id);
470
476
 
471
477
  let memberBalances: GenericBalance[] = [];
@@ -521,7 +527,13 @@ export class AuthenticatedStructures {
521
527
  }
522
528
  }
523
529
 
524
- const activeOrganizations = [...organizations.values()].filter(o => o.active);
530
+ const activeOrganizations: Organization[] = [];
531
+
532
+ for (const organization of organizations.values()) {
533
+ if (organization.active || await Context.auth.hasFullAccess(organization.id)) {
534
+ activeOrganizations.push(organization);
535
+ }
536
+ }
525
537
  const organizationStructs = await this.organizations(activeOrganizations);
526
538
 
527
539
  // Load missing groups
@@ -588,30 +600,44 @@ export class AuthenticatedStructures {
588
600
  return (await this.eventNotifications([eventNotification]))[0];
589
601
  }
590
602
 
591
- static async registrationsBlob(registrationData: {
592
- memberId: string;
593
- id: string;
594
- }[], members: MemberWithRegistrations[], includeContextOrganization = false, includeUser?: User): Promise<RegistrationsBlob> {
603
+ static async registrationsBlob(registrations: (Registration & { group: Group })[], members: MemberWithRegistrations[], includeContextOrganization = false, includeUser?: User): Promise<RegistrationsBlob> {
595
604
  const membersBlob = await this.membersBlob(members, includeContextOrganization, includeUser);
596
605
 
597
606
  const memberBlobs = membersBlob.members;
598
607
 
599
- const registrationWithMemberBlobs = registrationData.map(({ id, memberId }) => {
600
- const memberBlob = memberBlobs.find(m => m.id === memberId);
608
+ const registrationWithMemberBlobs = await Promise.all(registrations.map(async (registration) => {
609
+ const memberBlob = memberBlobs.find(m => m.id === registration.memberId);
601
610
  if (!memberBlob) {
602
611
  throw new Error('Member not found');
603
612
  }
604
613
 
605
- const registration = memberBlob.registrations.find(r => r.id === id);
606
- if (!registration) {
607
- throw new Error('Registration not found: ' + id);
614
+ let r = memberBlob.registrations.find(r => r.id === registration.id);
615
+
616
+ if (!r) {
617
+ const member = members.find(m => m.id === registration.memberId);
618
+ const balancesPermission = member ? (await Context.auth.hasFinancialMemberAccess(member, PermissionLevel.Read, Context.organization?.id)) : false;
619
+ r = registration.getStructure();
620
+ r.balances = balancesPermission
621
+ ? ((await CachedBalance.getForObjects([registration.id], null)).map((b) => {
622
+ return GenericBalance.create(b);
623
+ }))
624
+ : [];
625
+ r.group = await this.group(registration.group);
626
+
627
+ memberBlob.registrations.push(r);
628
+
629
+ // Add organization if missing
630
+ if (!membersBlob.organizations.find(o => o.id === r!.organizationId)) {
631
+ const organization = await Context.auth.getOrganization(r!.organizationId);
632
+ membersBlob.organizations.push(organization.getBaseStructure());
633
+ }
608
634
  }
609
635
 
610
636
  return RegistrationWithMemberBlob.create({
611
- ...registration,
637
+ ...r,
612
638
  member: memberBlob,
613
639
  });
614
- });
640
+ }));
615
641
 
616
642
  return RegistrationsBlob.create({
617
643
  registrations: registrationWithMemberBlobs,
@@ -5,7 +5,7 @@ import { memberFilterCompilers } from './members';
5
5
 
6
6
  export const memberJoin = SQL.join(Member.table).where(SQL.column(Member.table, 'id'), SQL.column(Registration.table, 'memberId'));
7
7
 
8
- const groupJoin = SQL.join(Group.table).where(SQL.column(Group.table, 'id'), SQL.column(Registration.table, 'groupId'));
8
+ export const groupJoin = SQL.join(Group.table).where(SQL.column(Group.table, 'id'), SQL.column(Registration.table, 'groupId'));
9
9
 
10
10
  export const registrationFilterCompilers: SQLFilterDefinitions = {
11
11
  ...baseSQLFilterCompilers,