@stamhoofd/backend 2.3.1 → 2.5.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 (39) hide show
  1. package/index.ts +3 -0
  2. package/package.json +4 -4
  3. package/src/endpoints/admin/invoices/GetInvoicesEndpoint.ts +1 -1
  4. package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +63 -2
  5. package/src/endpoints/auth/CreateAdminEndpoint.ts +6 -3
  6. package/src/endpoints/auth/GetOtherUserEndpoint.ts +41 -0
  7. package/src/endpoints/auth/GetUserEndpoint.ts +6 -28
  8. package/src/endpoints/auth/PatchUserEndpoint.ts +25 -6
  9. package/src/endpoints/auth/SignupEndpoint.ts +2 -2
  10. package/src/endpoints/global/email/CreateEmailEndpoint.ts +120 -0
  11. package/src/endpoints/global/email/GetEmailEndpoint.ts +51 -0
  12. package/src/endpoints/global/email/PatchEmailEndpoint.ts +108 -0
  13. package/src/endpoints/global/events/GetEventsEndpoint.ts +223 -0
  14. package/src/endpoints/global/events/PatchEventsEndpoint.ts +319 -0
  15. package/src/endpoints/global/members/GetMembersEndpoint.ts +124 -48
  16. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +107 -117
  17. package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +3 -2
  18. package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +2 -1
  19. package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +2 -1
  20. package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +2 -1
  21. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +9 -0
  22. package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +3 -2
  23. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +345 -176
  24. package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +5 -5
  25. package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +1 -1
  26. package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +43 -25
  27. package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +26 -7
  28. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +23 -22
  29. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -121
  30. package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +8 -8
  31. package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +2 -1
  32. package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +2 -1
  33. package/src/helpers/AdminPermissionChecker.ts +60 -3
  34. package/src/helpers/AuthenticatedStructures.ts +164 -37
  35. package/src/helpers/Context.ts +4 -0
  36. package/src/helpers/EmailResumer.ts +17 -0
  37. package/src/helpers/MemberUserSyncer.ts +221 -0
  38. package/src/seeds/1722256498-group-update-occupancy.ts +52 -0
  39. package/src/seeds/1722344160-update-membership.ts +57 -0
@@ -1,8 +1,10 @@
1
1
  import { SimpleError } from "@simonbackx/simple-errors";
2
- import { Group, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, OrganizationRegistrationPeriod, Payment, RegistrationPeriod, User, Webshop } from "@stamhoofd/models";
3
- import { MemberPlatformMembership as MemberPlatformMembershipStruct, MemberResponsibilityRecord as MemberResponsibilityRecordStruct, MemberWithRegistrationsBlob, MembersBlob, Organization as OrganizationStruct, PaymentGeneral, PermissionLevel, PrivateWebshop, User as UserStruct, WebshopPreview, Webshop as WebshopStruct } from '@stamhoofd/structures';
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';
4
5
 
5
6
  import { Context } from "./Context";
7
+ import { Formatter } from "@stamhoofd/utility";
6
8
 
7
9
  /**
8
10
  * Builds authenticated structures for the current user
@@ -51,10 +53,71 @@ export class AuthenticatedStructures {
51
53
  }
52
54
 
53
55
  static async group(group: Group) {
54
- if (!await Context.optionalAuth?.canAccessGroup(group)) {
55
- return group.getStructure()
56
+ return (await this.groups([group]))[0]
57
+ }
58
+
59
+ static async groups(groups: Group[]) {
60
+ const waitingListIds = Formatter.uniqueArray(groups.map(g => g.waitingListId).filter(id => id !== null) as string[])
61
+ const waitingLists = waitingListIds.length > 0 ? await Group.getByIDs(...waitingListIds) : []
62
+
63
+ const structs: GroupStruct[] = []
64
+ for (const group of groups) {
65
+ const waitingList = waitingLists.find(g => g.id == group.waitingListId) ?? null
66
+ const waitingListStruct = waitingList ? GroupStruct.create(waitingList) : null
67
+ if (waitingList && waitingListStruct && !await Context.optionalAuth?.canAccessGroup(waitingList)) {
68
+ waitingListStruct.privateSettings = null;
69
+ }
70
+
71
+ const struct = GroupStruct.create({
72
+ ...group,
73
+ waitingList: waitingListStruct
74
+ })
75
+
76
+ if (!await Context.optionalAuth?.canAccessGroup(group)) {
77
+ struct.privateSettings = null;
78
+ }
79
+
80
+ structs.push(struct)
81
+ }
82
+
83
+ return structs;
84
+ }
85
+
86
+ static async organizationRegistrationPeriods(organizationRegistrationPeriods: OrganizationRegistrationPeriod[]) {
87
+ if (organizationRegistrationPeriods.length === 0) {
88
+ return [];
89
+ }
90
+
91
+ const periodIds = Formatter.uniqueArray(organizationRegistrationPeriods.map(p => p.periodId))
92
+ const periods = await RegistrationPeriod.getByIDs(...periodIds)
93
+
94
+ const groupIds = Formatter.uniqueArray(organizationRegistrationPeriods.flatMap(p => p.settings.categories.flatMap(c => c.groupIds)))
95
+ const groups = groupIds.length ? await Group.getByIDs(...groupIds) : []
96
+
97
+ const groupStructs = await this.groups(groups)
98
+
99
+ const structs: OrganizationRegistrationPeriodStruct[] = []
100
+ for (const organizationPeriod of organizationRegistrationPeriods) {
101
+ const period = periods.find(p => p.id == organizationPeriod.periodId) ?? null
102
+ if (!period) {
103
+ continue
104
+ }
105
+ const groupIds = Formatter.uniqueArray(organizationPeriod.settings.categories.flatMap(c => c.groupIds))
106
+
107
+ structs.push(
108
+ OrganizationRegistrationPeriodStruct.create({
109
+ ...organizationPeriod,
110
+ period: period.getStructure(),
111
+ groups: groupStructs.filter(gg => groupIds.includes(gg.id)).sort(GroupStruct.defaultSort)
112
+ })
113
+ )
56
114
  }
57
- return group.getPrivateStructure()
115
+
116
+ return structs
117
+ }
118
+
119
+ static async organizationRegistrationPeriod(organizationRegistrationPeriod: OrganizationRegistrationPeriod) {
120
+ return (await this.organizationRegistrationPeriods([organizationRegistrationPeriod]))[0]
58
121
  }
59
122
 
60
123
  static async webshop(webshop: Webshop) {
@@ -65,8 +128,9 @@ export class AuthenticatedStructures {
65
128
  }
66
129
 
67
130
  static async organization(organization: Organization): Promise<OrganizationStruct> {
131
+ const organizationPeriod = await organization.getPeriod()
132
+
68
133
  if (await Context.optionalAuth?.canAccessPrivateOrganizationData(organization)) {
69
- const groups = await Group.getAll(organization.id, organization.periodId)
70
134
  const webshops = await Webshop.where({ organizationId: organization.id }, { select: Webshop.selectColumnsWithout(undefined, "products", "categories")})
71
135
  const webshopStructures: WebshopPreview[] = []
72
136
 
@@ -77,54 +141,82 @@ export class AuthenticatedStructures {
77
141
  webshopStructures.push(WebshopPreview.create(w))
78
142
  }
79
143
 
80
- const oPeriods = await OrganizationRegistrationPeriod.where({ periodId: organization.periodId }, {limit: 1})
81
- let oPeriod = oPeriods[0];
82
- const period = (await RegistrationPeriod.getByID(organization.periodId))!
83
-
84
- if (!oPeriod) {
85
- const organizationPeriod = new OrganizationRegistrationPeriod();
86
- organizationPeriod.organizationId = organization.id;
87
- organizationPeriod.periodId = period.id
88
- organizationPeriod.settings.categories = organization.meta.categories
89
- organizationPeriod.settings.rootCategoryId = organization.meta.rootCategoryId
90
- await organizationPeriod.save();
91
-
92
- oPeriod = organizationPeriod
93
- }
94
-
95
144
  return OrganizationStruct.create({
96
- id: organization.id,
97
- name: organization.name,
98
- meta: organization.meta,
99
- address: organization.address,
100
- registerDomain: organization.registerDomain,
101
- uri: organization.uri,
102
- website: organization.website,
145
+ ...organization.getBaseStructure(),
103
146
  privateMeta: organization.privateMeta,
104
147
  webshops: webshopStructures,
105
- createdAt: organization.createdAt,
106
- period: oPeriod.getStructure(period, groups)
148
+ period: await this.organizationRegistrationPeriod(organizationPeriod)
107
149
  })
108
150
  }
109
151
 
110
- return await organization.getStructure()
152
+ return OrganizationStruct.create({
153
+ ...organization.getBaseStructure(),
154
+ period: await this.organizationRegistrationPeriod(organizationPeriod)
155
+ })
111
156
  }
112
157
 
113
158
  static async adminOrganizations(organizations: Organization[]): Promise<OrganizationStruct[]> {
114
159
  const structs: OrganizationStruct[] = [];
115
- const admins = await User.getAdmins(organizations.map(o => o.id))
116
160
 
117
161
  for (const organization of organizations) {
118
- const base = await organization.getStructure({emptyGroups: true})
119
- base.admins = admins.filter(a => a.permissions?.organizationPermissions.has(organization.id)).map(a => UserStruct.create({...a, hasAccount: a.hasAccount()}))
162
+ const base = organization.getBaseStructure()
120
163
  structs.push(base)
121
164
  }
122
165
 
166
+ return Promise.resolve(structs)
167
+ }
168
+
169
+ static async userWithMembers(user: User): Promise<UserWithMembers> {
170
+ const members = await Member.getMembersWithRegistrationForUser(user)
171
+
172
+ return UserWithMembers.create({
173
+ ...user,
174
+ hasAccount: user.hasAccount(),
175
+
176
+ // Always include the current context organization - because it is possible we switch organization and we don't want to refetch every time
177
+ members: await this.membersBlob(members, true, user)
178
+ })
179
+ }
180
+
181
+ /**
182
+ * This version only returns connected members that are 1:1, skips other members
183
+ */
184
+ static async usersWithMembers(users: User[]): Promise<UserWithMembers[]> {
185
+ const structs: UserWithMembers[] = [];
186
+ const memberIds = Formatter.uniqueArray(users.map(u => u.memberId).filter(id => id !== null) as string[])
187
+ const members = memberIds.length > 0 ? await Member.getBlobByIds(...memberIds) : []
188
+
189
+ for (const user of users) {
190
+ const filteredMembers = user.memberId ? members.filter(m => m.id === user.memberId) : []
191
+ structs.push(UserWithMembers.create({
192
+ ...user,
193
+ hasAccount: user.hasAccount(),
194
+ members: await this.membersBlob(filteredMembers, false)
195
+ }))
196
+ }
197
+
123
198
  return structs
124
199
  }
125
200
 
126
- static async membersBlob(members: MemberWithRegistrations[], includeContextOrganization = false): Promise<MembersBlob> {
201
+ static async membersBlob(members: MemberWithRegistrations[], includeContextOrganization = false, includeUser?: User): Promise<MembersBlob> {
202
+ if (members.length === 0 && !includeUser) {
203
+ return MembersBlob.create({members: [], organizations: []})
204
+ }
127
205
  const organizations = new Map<string, Organization>()
206
+
207
+ if (includeUser) {
208
+ for (const organizationId of includeUser.permissions?.organizationPermissions.keys() ?? []) {
209
+ if (includeContextOrganization || organizationId !== Context.auth.organization?.id) {
210
+ const found = organizations.get(organizationId);
211
+ if (!found) {
212
+ const organization = await Context.auth.getOrganization(organizationId)
213
+ organizations.set(organization.id, organization)
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+
128
220
  const memberBlobs: MemberWithRegistrationsBlob[] = []
129
221
  for (const member of members) {
130
222
  for (const registration of member.registrations) {
@@ -137,6 +229,7 @@ export class AuthenticatedStructures {
137
229
  }
138
230
  }
139
231
 
232
+
140
233
  const blob = member.getStructureWithRegistrations()
141
234
  memberBlobs.push(
142
235
  await Context.auth.filterMemberData(member, blob)
@@ -145,10 +238,22 @@ export class AuthenticatedStructures {
145
238
 
146
239
  // Load responsibilities
147
240
  const responsibilities = members.length > 0 ? await MemberResponsibilityRecord.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
148
- const platformMemberships = members.length > 0 ? await MemberPlatformMembership.where({ memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
241
+ const platformMemberships = members.length > 0 ? await MemberPlatformMembership.where({ deletedAt: null, memberId: { sign: 'IN', value: members.map(m => m.id) } }) : []
242
+
243
+ // Load missing organizations
244
+ const organizationIds = Formatter.uniqueArray(responsibilities.map(r => r.organizationId).filter(id => id !== null) as string[])
245
+ for (const id of organizationIds) {
246
+ if (includeContextOrganization || id !== Context.auth.organization?.id) {
247
+ const found = organizations.get(id);
248
+ if (!found) {
249
+ const organization = await Context.auth.getOrganization(id)
250
+ organizations.set(organization.id, organization)
251
+ }
252
+ }
253
+ }
149
254
 
150
255
  for (const blob of memberBlobs) {
151
- blob.responsibilities = responsibilities.filter(r => r.memberId == blob.id).map(r => MemberResponsibilityRecordStruct.create(r))
256
+ blob.responsibilities = responsibilities.filter(r => r.memberId == blob.id).map(r => r.getStructure())
152
257
  blob.platformMemberships = platformMemberships.filter(r => r.memberId == blob.id).map(r => MemberPlatformMembershipStruct.create(r))
153
258
  }
154
259
 
@@ -157,4 +262,26 @@ export class AuthenticatedStructures {
157
262
  organizations: await Promise.all([...organizations.values()].map(o => this.organization(o)))
158
263
  })
159
264
  }
265
+
266
+ static async events(events: Event[]): Promise<EventStruct[]> {
267
+ // Load groups
268
+ const groupIds = events.map(e => e.groupId).filter(id => id !== null) as string[]
269
+ const groups = groupIds.length > 0 ? await Group.getByIDs(...groupIds) : []
270
+ const groupStructs = await this.groups(groups)
271
+
272
+ const result: EventStruct[] = []
273
+
274
+ for (const event of events) {
275
+ const group = groupStructs.find(g => g.id == event.groupId) ?? null
276
+
277
+ const struct = EventStruct.create({
278
+ ...event,
279
+ group
280
+ })
281
+
282
+ result.push(struct)
283
+ }
284
+
285
+ return result
286
+ }
160
287
  }
@@ -185,6 +185,10 @@ export class ContextInstance {
185
185
 
186
186
  const user = token.user
187
187
  this.user = user
188
+
189
+ // Load member of user
190
+ // todo
191
+
188
192
  this.#auth = new AdminPermissionChecker(user, await Platform.getSharedPrivateStruct(), this.organization);
189
193
 
190
194
  return {user, token};
@@ -0,0 +1,17 @@
1
+ import { Email } from "@stamhoofd/models";
2
+ import { SQL } from "@stamhoofd/sql";
3
+ import { EmailStatus } from "@stamhoofd/structures";
4
+
5
+ export async function resumeEmails() {
6
+ const query = SQL.select()
7
+ .from(SQL.table(Email.table))
8
+ .where(SQL.column('status'), EmailStatus.Sending);
9
+
10
+ const result = await query.fetch();
11
+ const emails = Email.fromRows(result, Email.table);
12
+
13
+ for (const email of emails) {
14
+ console.log('Resuming email that has sending status on boot', email.id);
15
+ await email.send()
16
+ }
17
+ }
@@ -0,0 +1,221 @@
1
+ import { Member, MemberResponsibilityRecord, MemberWithRegistrations, User } from "@stamhoofd/models";
2
+ import { SQL } from "@stamhoofd/sql";
3
+ import { Permissions, UserPermissions } from "@stamhoofd/structures";
4
+
5
+ export class MemberUserSyncerStatic {
6
+ /**
7
+ * Call when:
8
+ * - responsibilities have changed
9
+ * - email addresses have changed
10
+ */
11
+ async onChangeMember(member: MemberWithRegistrations) {
12
+ const userEmails = [...member.details.alternativeEmails]
13
+
14
+ if (member.details.email) {
15
+ userEmails.push(member.details.email)
16
+ }
17
+
18
+ const parentEmails = member.details.parentsHaveAccess ? member.details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails) : []
19
+
20
+ // Make sure all these users have access to the member
21
+ for (const email of userEmails) {
22
+ // Link users that are found with these email addresses.
23
+ await this.linkUser(email, member, false)
24
+ }
25
+
26
+ for (const email of parentEmails) {
27
+ // Link parents
28
+ await this.linkUser(email, member, true)
29
+ }
30
+
31
+ // Remove access of users that are not in this list
32
+ for (const user of member.users) {
33
+ if (!userEmails.includes(user.email) && !parentEmails.includes(user.email)) {
34
+ await this.unlinkUser(user, member)
35
+ }
36
+ }
37
+
38
+ }
39
+
40
+ async onDeleteMember(member: MemberWithRegistrations) {
41
+ for (const u of member.users) {
42
+ console.log("Unlinking user "+u.email+" from deleted member "+member.id)
43
+ await this.unlinkUser(u, member)
44
+ }
45
+ }
46
+
47
+ async getResponsibilitiesForMembers(memberIds: string[]) {
48
+ const rows = await SQL.select()
49
+ .from(SQL.table(MemberResponsibilityRecord.table))
50
+ .where(SQL.column("memberId"), memberIds)
51
+ .where(SQL.column("endDate"), null)
52
+ .fetch();
53
+
54
+ return MemberResponsibilityRecord.fromRows(rows, MemberResponsibilityRecord.table)
55
+ }
56
+
57
+ async updateInheritedPermissions(user: User) {
58
+ const responsibilities = user.memberId ? (await this.getResponsibilitiesForMembers([user.memberId])) : []
59
+
60
+ // Check if the member has active registrations
61
+ // otherwise -> do not inherit any permissions
62
+
63
+ user.permissions = user.permissions ?? UserPermissions.create({})
64
+
65
+ // Group responsibilities by organization
66
+ const responsibilitiesByOrganization: Map<string|null, MemberResponsibilityRecord[]> = new Map()
67
+
68
+ responsibilitiesByOrganization.set(null, [])
69
+
70
+ for (const organizationId of user.permissions.organizationPermissions.keys()) {
71
+ // Make sure we reset responsibilities for organizations that are not in the list
72
+ responsibilitiesByOrganization.set(organizationId, [])
73
+ }
74
+
75
+ for (const responsibility of responsibilities) {
76
+ const v = responsibilitiesByOrganization.get(responsibility.organizationId) ?? []
77
+ responsibilitiesByOrganization.set(responsibility.organizationId, v)
78
+ v.push(responsibility)
79
+ }
80
+
81
+ for (const organizationId of responsibilitiesByOrganization.keys()) {
82
+ if (organizationId === null) {
83
+ const patch = user.permissions.convertPlatformPatch(
84
+ Permissions.patch({
85
+ responsibilities: (responsibilitiesByOrganization.get(organizationId) ?? []).map(r => r.getStructure()) as any
86
+ })
87
+ )
88
+ user.permissions = user.permissions.patch(patch)
89
+ } else {
90
+ const patch = user.permissions.convertPatch(
91
+ Permissions.patch({
92
+ responsibilities: (responsibilitiesByOrganization.get(organizationId) ?? []).map(r => r.getStructure()) as any
93
+ }),
94
+ organizationId
95
+ )
96
+ user.permissions = user.permissions.patch(patch)
97
+ }
98
+
99
+ }
100
+
101
+ // Platform permissions
102
+ user.permissions.clearEmptyPermissions()
103
+
104
+ if (user.permissions.isEmpty) {
105
+ user.permissions = null;
106
+ }
107
+
108
+ await user.save()
109
+ }
110
+
111
+ async unlinkUser(user: User, member: MemberWithRegistrations) {
112
+ console.log("Removing access for "+ user.id +" to member "+member.id)
113
+ await Member.users.reverse("members").unlink(user, member)
114
+
115
+ if (user.memberId === member.id) {
116
+ user.memberId = null;
117
+ await user.save()
118
+ }
119
+
120
+ // Update model relation to correct response
121
+ const existingIndex = member.users.findIndex(u => u.id === user.id)
122
+ if (existingIndex !== -1) {
123
+ member.users.splice(existingIndex, 1)
124
+ }
125
+
126
+ await this.updateInheritedPermissions(user)
127
+ }
128
+
129
+ async linkUser(email: string, member: MemberWithRegistrations, asParent: boolean) {
130
+ let user = member.users.find(u => u.email.toLocaleLowerCase() === email.toLocaleLowerCase()) ?? await User.getForAuthentication(member.organizationId, email, {allowWithoutAccount: true})
131
+
132
+ if (user) {
133
+ console.log("Giving an existing user access to a member: " + user.id + ' - ' + member.id)
134
+ if (!asParent) {
135
+ if (user.memberId && user.memberId !== member.id) {
136
+ console.error('Found conflicting user with multiple members', user.id, 'members', user.memberId, 'to', member.id)
137
+
138
+ const otherMember = await Member.getWithRegistrations(user.memberId)
139
+
140
+ if (otherMember) {
141
+ if (otherMember.registrations.length > 0 && member.registrations.length === 0) {
142
+ // Choose the other member
143
+ // don't make changes
144
+ console.error('Resolved to current member - no changes made')
145
+ return
146
+ }
147
+
148
+ const responsibilities = await this.getResponsibilitiesForMembers([otherMember.id, member.id])
149
+ const responsibilitiesOther = responsibilities.filter(r => r.memberId === otherMember.id)
150
+ const responsibilitiesCurrent = responsibilities.filter(r => r.memberId === member.id)
151
+
152
+ if (responsibilitiesOther.length >= responsibilitiesCurrent.length) {
153
+ console.error('Resolved to current member because of more responsibilities - no changes made')
154
+ return
155
+ }
156
+ }
157
+ }
158
+ user.firstName = member.details.firstName
159
+ user.lastName = member.details.lastName
160
+ user.memberId = member.id;
161
+ await this.updateInheritedPermissions(user)
162
+ } else {
163
+ if (user.memberId === member.id) {
164
+ // Unlink: parents are never 'equal' to the member
165
+ user.memberId = null;
166
+ await this.updateInheritedPermissions(user)
167
+ }
168
+
169
+ if (!user.firstName && !user.lastName) {
170
+ const parents = member.details.parents.filter(p => p.email === email)
171
+ if (parents.length === 1) {
172
+ user.firstName = parents[0].firstName
173
+ user.lastName = parents[0].lastName
174
+ await user.save()
175
+ }
176
+ }
177
+
178
+ if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
179
+ user.firstName = null;
180
+ user.lastName = null;
181
+ await user.save()
182
+ }
183
+ }
184
+ } else {
185
+ // Create a new placeholder user
186
+ user = new User()
187
+ user.organizationId = member.organizationId
188
+ user.email = email
189
+
190
+ if (!asParent) {
191
+ user.firstName = member.details.firstName
192
+ user.lastName = member.details.lastName
193
+ user.memberId = member.id;
194
+ await this.updateInheritedPermissions(user)
195
+ } else {
196
+ const parents = member.details.parents.filter(p => p.email === email)
197
+ if (parents.length === 1) {
198
+ user.firstName = parents[0].firstName
199
+ user.lastName = parents[0].lastName
200
+ }
201
+
202
+ if (user.firstName === member.details.firstName && user.lastName === member.details.lastName) {
203
+ user.firstName = null;
204
+ user.lastName = null;
205
+ }
206
+
207
+ await user.save()
208
+ }
209
+
210
+ console.log("Created new (placeholder) user that has access to a member: "+user.id)
211
+ }
212
+
213
+ // Update model relation to correct response
214
+ if (!member.users.find(u => u.id === user.id)) {
215
+ await Member.users.reverse("members").link(user, [member])
216
+ member.users.push(user)
217
+ }
218
+ }
219
+ }
220
+
221
+ export const MemberUserSyncer = new MemberUserSyncerStatic()
@@ -0,0 +1,52 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { Group } from '@stamhoofd/models';
3
+
4
+ export default new Migration(async () => {
5
+ if (STAMHOOFD.environment == "test") {
6
+ console.log("skipped in tests")
7
+ return;
8
+ }
9
+
10
+ if(STAMHOOFD.userMode !== "platform") {
11
+ console.log("skipped seed group-update-occupancy because usermode not platform")
12
+ return;
13
+ }
14
+
15
+ process.stdout.write('\n');
16
+ let c = 0;
17
+ let id: string = '';
18
+
19
+ while(true) {
20
+ const rawGroups = await Group.where({
21
+ id: {
22
+ value: id,
23
+ sign: '>'
24
+ }
25
+ }, {limit: 100, sort: ['id']});
26
+
27
+ const groups = await Group.getByIDs(...rawGroups.map(g => g.id));
28
+
29
+ for (const group of groups) {
30
+ await group.updateOccupancy();
31
+ await group.save();
32
+
33
+ c++;
34
+
35
+ if (c%1000 === 0) {
36
+ process.stdout.write('.');
37
+ }
38
+ if (c%10000 === 0) {
39
+ process.stdout.write('\n');
40
+ }
41
+ }
42
+
43
+ if (groups.length === 0) {
44
+ break;
45
+ }
46
+
47
+ id = groups[groups.length - 1].id;
48
+ }
49
+
50
+ // Do something here
51
+ return Promise.resolve()
52
+ })
@@ -0,0 +1,57 @@
1
+ import { Migration } from '@simonbackx/simple-database';
2
+ import { Member } from '@stamhoofd/models';
3
+
4
+ export default new Migration(async () => {
5
+ if (STAMHOOFD.environment == "test") {
6
+ console.log("skipped in tests")
7
+ return;
8
+ }
9
+
10
+ if(STAMHOOFD.userMode !== "platform") {
11
+ console.log("skipped seed update-membership because usermode not platform")
12
+ return;
13
+ }
14
+
15
+ process.stdout.write('\n');
16
+ let c = 0;
17
+ let id: string = '';
18
+
19
+ while(true) {
20
+ const rawMembers = await Member.where({
21
+ id: {
22
+ value: id,
23
+ sign: '>'
24
+ }
25
+ }, {limit: 100, sort: ['id']});
26
+
27
+ // const members = await Member.getByIDs(...rawMembers.map(m => m.id));
28
+
29
+ for (const member of rawMembers) {
30
+ const memberWithRegistrations = await Member.getWithRegistrations(member.id);
31
+ if(memberWithRegistrations) {
32
+ await memberWithRegistrations.updateMemberships();
33
+ await memberWithRegistrations.save();
34
+ } else {
35
+ throw new Error("Member with registrations not found: " + member.id);
36
+ }
37
+
38
+ c++;
39
+
40
+ if (c%1000 === 0) {
41
+ process.stdout.write('.');
42
+ }
43
+ if (c%10000 === 0) {
44
+ process.stdout.write('\n');
45
+ }
46
+ }
47
+
48
+ if (rawMembers.length === 0) {
49
+ break;
50
+ }
51
+
52
+ id = rawMembers[rawMembers.length - 1].id;
53
+ }
54
+
55
+ // Do something here
56
+ return Promise.resolve()
57
+ })