@stamhoofd/backend 2.17.3 → 2.18.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamhoofd/backend",
3
- "version": "2.17.3",
3
+ "version": "2.18.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -39,10 +39,10 @@
39
39
  "@stamhoofd/backend-i18n": "^2.17.0",
40
40
  "@stamhoofd/backend-middleware": "^2.17.0",
41
41
  "@stamhoofd/email": "^2.17.0",
42
- "@stamhoofd/models": "^2.17.3",
42
+ "@stamhoofd/models": "^2.18.0",
43
43
  "@stamhoofd/queues": "^2.17.3",
44
- "@stamhoofd/sql": "^2.17.3",
45
- "@stamhoofd/structures": "^2.17.3",
44
+ "@stamhoofd/sql": "^2.18.0",
45
+ "@stamhoofd/structures": "^2.18.0",
46
46
  "@stamhoofd/utility": "^2.17.0",
47
47
  "archiver": "^7.0.1",
48
48
  "aws-sdk": "^2.885.0",
@@ -60,5 +60,5 @@
60
60
  "postmark": "4.0.2",
61
61
  "stripe": "^16.6.0"
62
62
  },
63
- "gitHead": "9aa2fbb9c268c4c5c45efd68a9dc55f5d48eb621"
63
+ "gitHead": "a47aa5bb6ad71d1e6a2f88bc203caa5acf4f497f"
64
64
  }
@@ -0,0 +1,9 @@
1
+ import { SetupStepUpdater } from "../helpers/SetupStepsUpdater";
2
+
3
+ export async function updateSetupSteps() {
4
+ if (STAMHOOFD.userMode !== "platform") {
5
+ return;
6
+ }
7
+
8
+ await SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod();
9
+ }
@@ -63,12 +63,8 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
63
63
  new SQLAlias('data__price')
64
64
  )
65
65
  )
66
- .from(
67
- SQL.table('member_platform_memberships')
68
- )
69
- .where(SQL.column('invoiceId'), null)
70
- .andWhere(SQL.column('invoiceItemDetailId'), null);
71
-
66
+ .from('member_platform_memberships')
67
+ .where('balanceItemId', null);
72
68
 
73
69
  const result = await query.fetch();
74
70
  const members = result[0]['data']['members'] as number;
@@ -122,17 +118,13 @@ export class GetChargeMembershipsSummaryEndpoint extends Endpoint<Params, Query,
122
118
  new SQLAlias('data__price')
123
119
  )
124
120
  )
125
- .from(
126
- SQL.table('member_platform_memberships')
121
+ .from('member_platform_memberships')
122
+ .where('balanceItemId', null)
123
+ .groupBy(
124
+ SQL.column('member_platform_memberships', 'membershipTypeId')
127
125
  );
128
- query.where(SQL.column('invoiceId'), null)
129
- query.andWhere(SQL.column('invoiceItemDetailId'), null)
130
- query.groupBy(SQL.column('member_platform_memberships', 'membershipTypeId'));
131
-
132
126
 
133
127
  const result = await query.fetch();
134
- console.log(result);
135
-
136
128
  const membershipsPerType = new Map<string, ChargeMembershipsTypeSummary>();
137
129
 
138
130
  for (const row of result) {
@@ -160,7 +160,7 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
160
160
  const writer = new XlsxWriter(zipWriterAdapter);
161
161
 
162
162
  // Limit to pages of 100
163
- request.filter.limit = 100;
163
+ request.filter.limit = STAMHOOFD.environment === 'development' ? 1 : 100; // in development, we need to check if total count matches and pagination is working correctly
164
164
 
165
165
  await exportToExcel({
166
166
  definitions: loader.sheets,
@@ -1,10 +1,11 @@
1
1
  import { AutoEncoderPatchType, Decoder, patchObject } from "@simonbackx/simple-encoding";
2
2
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
3
- import { Platform, RegistrationPeriod } from "@stamhoofd/models";
4
- import { Platform as PlatformStruct } from "@stamhoofd/structures";
3
+ import { Organization, Platform, RegistrationPeriod } from "@stamhoofd/models";
4
+ import { PlatformPremiseType, Platform as PlatformStruct } from "@stamhoofd/structures";
5
5
 
6
- import { Context } from "../../../helpers/Context";
7
6
  import { SimpleError } from "@simonbackx/simple-errors";
7
+ import { Context } from "../../../helpers/Context";
8
+ import { SetupStepUpdater } from "../../../helpers/SetupStepsUpdater";
8
9
 
9
10
  type Params = Record<string, never>;
10
11
  type Query = undefined;
@@ -64,7 +65,18 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
64
65
  }
65
66
 
66
67
  // Update config
67
- platform.config = patchObject(platform.config, request.body.config)
68
+ if(request.body.config.premiseTypes) {
69
+ const oldConfig = platform.config.clone();
70
+ platform.config = patchObject(platform.config, request.body.config);
71
+ const newPremiseTypes = platform.config.premiseTypes;
72
+
73
+ // update setup step premise types
74
+ if(this.shouldUpdateSetupStepPremise(newPremiseTypes, oldConfig.premiseTypes)) {
75
+ await SetupStepUpdater.updateSetupStepsForAllOrganizationsInCurrentPeriod({premiseTypes: newPremiseTypes});
76
+ }
77
+ } else {
78
+ platform.config = patchObject(platform.config, request.body.config)
79
+ }
68
80
  }
69
81
 
70
82
  if (request.body.period && request.body.period.id !== platform.periodId) {
@@ -78,7 +90,57 @@ export class PatchPlatformEndpoint extends Endpoint<Params, Query, Body, Respons
78
90
  platform.periodId = period.id
79
91
  }
80
92
 
93
+ if (request.body.membershipOrganizationId !== undefined) {
94
+ if (!Context.auth.hasPlatformFullAccess()) {
95
+ throw Context.auth.error()
96
+ }
97
+
98
+ if (request.body.membershipOrganizationId) {
99
+ const organization = await Organization.getByID(request.body.membershipOrganizationId)
100
+ if (!organization) {
101
+ throw new SimpleError({
102
+ code: "invalid_organization",
103
+ message: "Invalid organization"
104
+ })
105
+ }
106
+ platform.membershipOrganizationId = organization.id
107
+ } else {
108
+ platform.membershipOrganizationId = null
109
+ }
110
+ }
111
+
81
112
  await platform.save()
82
113
  return new Response(await Platform.getSharedPrivateStruct());
83
114
  }
115
+
116
+ private shouldUpdateSetupStepPremise(newPremiseTypes: PlatformPremiseType[], oldPremiseTypes: PlatformPremiseType[]) {
117
+ for(const premiseType of newPremiseTypes) {
118
+ const id = premiseType.id;
119
+ const oldVersion = oldPremiseTypes.find(x => x.id === id);
120
+
121
+ // if premise type is not new
122
+ if(oldVersion) {
123
+ if(oldVersion.min !== premiseType.min || oldVersion.max !== premiseType.max) {
124
+ return true;
125
+ }
126
+ continue;
127
+ }
128
+
129
+ // if premise type is new
130
+ if(premiseType.min || premiseType.max) {
131
+ return true;
132
+ }
133
+ }
134
+
135
+ for(const oldPremiseType of oldPremiseTypes) {
136
+ const id = oldPremiseType.id;
137
+
138
+ // if premise type is removed
139
+ if(!newPremiseTypes.some(x => x.id === id)) {
140
+ if(oldPremiseType.min || oldPremiseType.max) {
141
+ return true;
142
+ }
143
+ }
144
+ }
145
+ }
84
146
  }
@@ -1,14 +1,11 @@
1
1
  import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
- import { SimpleError } from "@simonbackx/simple-errors";
3
- import { STInvoice } from "@stamhoofd/models";
4
- import { Token } from "@stamhoofd/models";
5
- import { STBillingStatus } from "@stamhoofd/structures";
2
+ import { OrganizationBillingStatus } from "@stamhoofd/structures";
6
3
 
7
4
  import { Context } from "../../../../helpers/Context";
8
5
 
9
6
  type Params = Record<string, never>;
10
7
  type Query = undefined;
11
- type ResponseBody = STBillingStatus;
8
+ type ResponseBody = OrganizationBillingStatus;
12
9
  type Body = undefined;
13
10
 
14
11
  export class GetBillingStatusEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
@@ -32,8 +29,10 @@ export class GetBillingStatusEndpoint extends Endpoint<Params, Query, Body, Resp
32
29
  // If the user has permission, we'll also search if he has access to the organization's key
33
30
  if (!await Context.auth.canManageFinances(organization.id)) {
34
31
  throw Context.auth.error()
35
- }
32
+ }
36
33
 
37
- return new Response(await STInvoice.getBillingStatus(organization));
34
+ return new Response(
35
+ OrganizationBillingStatus.create({})
36
+ )
38
37
  }
39
38
  }
@@ -0,0 +1,78 @@
1
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { OrganizationDetailedBillingStatus, OrganizationDetailedBillingStatusItem, PaymentMethod } from "@stamhoofd/structures";
3
+
4
+ import { BalanceItem, Organization, Payment } from "@stamhoofd/models";
5
+ import { SQL } from "@stamhoofd/sql";
6
+ import { Formatter } from "@stamhoofd/utility";
7
+ import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
8
+ import { Context } from "../../../../helpers/Context";
9
+
10
+ type Params = Record<string, never>;
11
+ type Query = undefined;
12
+ type ResponseBody = OrganizationDetailedBillingStatus;
13
+ type Body = undefined;
14
+
15
+ export class GetDetailedBillingStatusEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
16
+ protected doesMatch(request: Request): [true, Params] | [false] {
17
+ if (request.method != "GET") {
18
+ return [false];
19
+ }
20
+
21
+ const params = Endpoint.parseParameters(request.url, "/billing/status/detailed", {});
22
+
23
+ if (params) {
24
+ return [true, params as Params];
25
+ }
26
+ return [false];
27
+ }
28
+
29
+ async handle(_: DecodedRequest<Params, Query, Body>) {
30
+ const organization = await Context.setOrganizationScope();
31
+ await Context.authenticate()
32
+
33
+ // If the user has permission, we'll also search if he has access to the organization's key
34
+ if (!await Context.auth.canManageFinances(organization.id)) {
35
+ throw Context.auth.error()
36
+ }
37
+
38
+ const balanceItemModels = await BalanceItem.balanceItemsForOrganization(organization.id);
39
+
40
+ // Hide pending online payments
41
+ const paymentModels = await Payment.select()
42
+ .where('payingOrganizationId', organization.id)
43
+ .andWhere(
44
+ SQL.whereNot('paidAt', null)
45
+ .or('method', [PaymentMethod.Transfer, PaymentMethod.DirectDebit, PaymentMethod.PointOfSale, PaymentMethod.Unknown])
46
+ )
47
+ .fetch()
48
+
49
+ const organizationIds = Formatter.uniqueArray([
50
+ ...balanceItemModels.map(b => b.organizationId),
51
+ ...paymentModels.map(p => p.organizationId).filter(p => p !== null)
52
+ ])
53
+
54
+ // Group by organization you'll have to pay to
55
+ if (organizationIds.length === 0) {
56
+ return new Response(
57
+ OrganizationDetailedBillingStatus.create({})
58
+ )
59
+ }
60
+
61
+ const balanceItems = await BalanceItem.getStructureWithPayments(balanceItemModels)
62
+ const organizationModels = await Organization.getByIDs(...organizationIds)
63
+ const organizations = await AuthenticatedStructures.organizations(organizationModels)
64
+ const payments = await AuthenticatedStructures.paymentsGeneral(paymentModels, false)
65
+
66
+ return new Response(
67
+ OrganizationDetailedBillingStatus.create({
68
+ organizations: organizations.map(o => {
69
+ return OrganizationDetailedBillingStatusItem.create({
70
+ organization: o,
71
+ balanceItems: balanceItems.filter(b => b.organizationId == o.id),
72
+ payments: payments.filter(p => p.organizationId === o.id)
73
+ })
74
+ })
75
+ })
76
+ )
77
+ }
78
+ }
@@ -8,6 +8,7 @@ import { Formatter } from '@stamhoofd/utility';
8
8
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
9
9
  import { BuckarooHelper } from '../../../../helpers/BuckarooHelper';
10
10
  import { Context } from '../../../../helpers/Context';
11
+ import { SetupStepUpdater } from '../../../../helpers/SetupStepsUpdater';
11
12
  import { ViesHelper } from '../../../../helpers/ViesHelper';
12
13
 
13
14
  type Params = Record<string, never>;
@@ -100,6 +101,9 @@ export class PatchOrganizationEndpoint extends Endpoint<Params, Query, Body, Res
100
101
  if (request.body.privateMeta && request.body.privateMeta.isPatch()) {
101
102
  organization.privateMeta.emails = request.body.privateMeta.emails.applyTo(organization.privateMeta.emails)
102
103
  organization.privateMeta.premises = patchObject(organization.privateMeta.premises, request.body.privateMeta.premises);
104
+ if(request.body.privateMeta.premises) {
105
+ await SetupStepUpdater.updateForOrganization(organization);
106
+ }
103
107
  organization.privateMeta.roles = request.body.privateMeta.roles.applyTo(organization.privateMeta.roles)
104
108
  organization.privateMeta.responsibilities = request.body.privateMeta.responsibilities.applyTo(organization.privateMeta.responsibilities)
105
109
  organization.privateMeta.inheritedResponsibilityRoles = request.body.privateMeta.inheritedResponsibilityRoles.applyTo(organization.privateMeta.inheritedResponsibilityRoles)
@@ -4,7 +4,7 @@ import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-
4
4
  import { SimpleError } from '@simonbackx/simple-errors';
5
5
  import { Payment } from '@stamhoofd/models';
6
6
  import { SQL, compileToSQLFilter, compileToSQLSorter } from "@stamhoofd/sql";
7
- import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, StamhoofdFilter, getSortFilter } from '@stamhoofd/structures';
7
+ import { CountFilteredRequest, LimitedFilteredRequest, PaginatedResponse, PaymentGeneral, StamhoofdFilter, assertSort, getSortFilter } from '@stamhoofd/structures';
8
8
 
9
9
  import { AuthenticatedStructures } from '../../../../helpers/AuthenticatedStructures';
10
10
  import { Context } from '../../../../helpers/Context';
@@ -134,7 +134,11 @@ export class GetPaymentsEndpoint extends Endpoint<Params, Query, Body, ResponseB
134
134
  query.where(compileToSQLFilter(q.pageFilter, filterCompilers))
135
135
  }
136
136
 
137
- query.orderBy(compileToSQLSorter(q.sort, sorters))
137
+ query.orderBy(compileToSQLSorter(assertSort(q.sort, [
138
+ {
139
+ key: 'id'
140
+ }
141
+ ]), sorters))
138
142
  query.limit(q.limit)
139
143
  }
140
144
 
@@ -0,0 +1,78 @@
1
+ import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
2
+ import { OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, SetupStepType } from "@stamhoofd/structures";
3
+
4
+ import { AutoEncoder, BooleanDecoder, Decoder, EnumDecoder, field } from "@simonbackx/simple-encoding";
5
+ import { SimpleError } from "@simonbackx/simple-errors";
6
+ import { OrganizationRegistrationPeriod } from "@stamhoofd/models";
7
+ import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
8
+ import { Context } from "../../../../helpers/Context";
9
+
10
+ type Params = { readonly id: string };
11
+ type Query = undefined;
12
+
13
+ class Body extends AutoEncoder {
14
+ @field({decoder: new EnumDecoder(SetupStepType)})
15
+ type: SetupStepType
16
+
17
+ @field({decoder: BooleanDecoder})
18
+ isReviewed: boolean
19
+ }
20
+
21
+ type ResponseBody = OrganizationRegistrationPeriodStruct
22
+
23
+ /**
24
+ * Endpoint to mark a setup step as reviewed
25
+ */
26
+
27
+ export class SetupStepReviewEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
28
+ bodyDecoder = Body as Decoder<Body>;
29
+
30
+ protected doesMatch(request: Request): [true, Params] | [false] {
31
+ if (request.method != "POST") {
32
+ return [false];
33
+ }
34
+
35
+ const params = Endpoint.parseParameters(request.url, "/organization/registration-period/@id/setup-steps/review", {id: String});
36
+
37
+ if (params) {
38
+ return [true, params as Params];
39
+ }
40
+ return [false];
41
+ }
42
+
43
+ async handle(request: DecodedRequest<Params, Query, Body>) {
44
+ const organization = await Context.setOrganizationScope();
45
+ await Context.authenticate()
46
+
47
+ if (!await Context.auth.hasFullAccess(organization.id)) {
48
+ throw Context.auth.error()
49
+ }
50
+
51
+ const periodId = request.params.id;
52
+ const stepType = request.body.type;
53
+ const isReviewed = request.body.isReviewed;
54
+
55
+ const organizationPeriod = await OrganizationRegistrationPeriod.getByID(periodId);
56
+ if (!organizationPeriod || organizationPeriod.organizationId !== organization.id) {
57
+ throw new SimpleError({
58
+ code: "not_found",
59
+ message: "Period not found",
60
+ statusCode: 404
61
+ })
62
+ }
63
+
64
+ const setupSteps = organizationPeriod.setupSteps;
65
+
66
+ if(isReviewed) {
67
+ setupSteps.markReviewed(stepType);
68
+ } else {
69
+ setupSteps.resetReviewed(stepType);
70
+ }
71
+
72
+ await organizationPeriod.save();
73
+
74
+ return new Response(
75
+ await AuthenticatedStructures.organizationRegistrationPeriod(organizationPeriod),
76
+ );
77
+ }
78
+ }
@@ -1,4 +1,4 @@
1
- import { XlsxBuiltInNumberFormat, XlsxTransformerColumn } from "@stamhoofd/excel-writer";
1
+ import { XlsxBuiltInNumberFormat, XlsxTransformerColumn, XlsxTransformerConcreteColumn } from "@stamhoofd/excel-writer";
2
2
  import { StripeAccount as StripeAccountStruct, BalanceItemPaymentDetailed, BalanceItemRelationType, CountryHelper, ExcelExportType, getBalanceItemRelationTypeName, getBalanceItemTypeName, PaymentGeneral, PaymentMethodHelper, PaymentStatusHelper, PaginatedResponse, PaymentProvider } from "@stamhoofd/structures";
3
3
  import { ExportToExcelEndpoint } from "../endpoints/global/files/ExportToExcelEndpoint";
4
4
  import { GetPaymentsEndpoint } from "../endpoints/organization/dashboard/payments/GetPaymentsEndpoint";
@@ -52,12 +52,44 @@ ExportToExcelEndpoint.loaders.set(ExcelExportType.Payments, {
52
52
  {
53
53
  id: 'balanceItemPayments',
54
54
  name: 'Betaallijnen',
55
- transform: (data: PaymentGeneral) => data.balanceItemPayments.map(p => ({
55
+ transform: (data: PaymentGeneral): PaymentWithItem[] => data.balanceItemPayments.map(p => ({
56
56
  payment: data,
57
57
  balanceItemPayment: p
58
58
  })),
59
59
  columns: [
60
- ...getBalanceItemColumns()
60
+ ...getBalanceItemColumns(),
61
+
62
+ // Repeating columns need to de-transform again
63
+ ...[
64
+ ...getGeneralColumns(),
65
+ ...getInvoiceColumns(),
66
+ ].map(c => {
67
+ if ('match' in c) {
68
+ return {
69
+ ...c,
70
+ match: (id: string) => {
71
+ const result = c.match(id)
72
+ if (!result) {
73
+ return result;
74
+ }
75
+
76
+ return result.map(cc => ({
77
+ ...cc,
78
+ getValue: (object: PaymentWithItem) => {
79
+ return cc.getValue(object.payment)
80
+ },
81
+ }))
82
+ },
83
+ }
84
+ }
85
+
86
+ return {
87
+ ...c,
88
+ getValue: (object: PaymentWithItem) => {
89
+ return c.getValue(object.payment)
90
+ },
91
+ }
92
+ })
61
93
  ]
62
94
  }
63
95
  ]
@@ -164,7 +196,7 @@ function getBalanceItemColumns(): XlsxTransformerColumn<PaymentWithItem>[] {
164
196
  }
165
197
 
166
198
 
167
- function getGeneralColumns(): XlsxTransformerColumn<PaymentGeneral>[] {
199
+ function getGeneralColumns(): XlsxTransformerConcreteColumn<PaymentGeneral>[] {
168
200
  return [
169
201
  {
170
202
  id: 'id',
@@ -1,10 +1,9 @@
1
1
  import { SimpleError } from "@simonbackx/simple-errors";
2
2
  import { Event, Group, Member, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from "@stamhoofd/models";
3
- import { Event as EventStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, MemberWithRegistrationsBlob, MembersBlob, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateWebshop, User as UserStruct, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
4
- import { OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, GroupCategory, GroupPrivateSettings, GroupSettings, GroupStatus, Group as GroupStruct, GroupType } from '@stamhoofd/structures';
3
+ import { Event as EventStruct, Group as GroupStruct, MemberPlatformMembership as MemberPlatformMembershipStruct, MemberWithRegistrationsBlob, MembersBlob, OrganizationRegistrationPeriod as OrganizationRegistrationPeriodStruct, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateWebshop, UserWithMembers, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
5
4
 
6
- import { Context } from "./Context";
7
5
  import { Formatter } from "@stamhoofd/utility";
6
+ import { Context } from "./Context";
8
7
 
9
8
  /**
10
9
  * Builds authenticated structures for the current user
@@ -55,7 +54,7 @@ export class AuthenticatedStructures {
55
54
  }
56
55
 
57
56
  static async groups(groups: Group[]) {
58
- const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null) as string[])
57
+ const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null))
59
58
  const waitingLists = waitingListIds.length > 0 ? await Group.getByIDs(...waitingListIds) : []
60
59
 
61
60
  const structs: GroupStruct[] = []
@@ -194,7 +193,7 @@ export class AuthenticatedStructures {
194
193
  */
195
194
  static async usersWithMembers(users: User[]): Promise<UserWithMembers[]> {
196
195
  const structs: UserWithMembers[] = [];
197
- const memberIds = Formatter.uniqueArray(users.map(u => u.memberId).filter(id => id !== null) as string[])
196
+ const memberIds = Formatter.uniqueArray(users.map(u => u.memberId).filter(id => id !== null))
198
197
  const members = memberIds.length > 0 ? await Member.getBlobByIds(...memberIds) : []
199
198
 
200
199
  for (const user of users) {
@@ -251,7 +250,7 @@ export class AuthenticatedStructures {
251
250
  const platformMemberships = members.length > 0 ? await MemberPlatformMembership.where({ deletedAt: null, memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
252
251
 
253
252
  // Load missing organizations
254
- const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).filter(id => id !== null) as string[])
253
+ const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).filter(id => id !== null))
255
254
  for (const id of organizationIds) {
256
255
  if (includeContextOrganization || id !== Context.auth.organization?.id) {
257
256
  const found = organizations.get(id);
@@ -275,7 +274,7 @@ export class AuthenticatedStructures {
275
274
 
276
275
  static async events(events: Event[]): Promise<EventStruct[]> {
277
276
  // Load groups
278
- const groupIds = events.map(e => e.groupId).filter(id => id !== null) as string[]
277
+ const groupIds = events.map(e => e.groupId).filter(id => id !== null)
279
278
  const groups = groupIds.length > 0 ? await Group.getByIDs(...groupIds) : []
280
279
  const groupStructs = await this.groups(groups)
281
280