@stamhoofd/backend 2.6.0 → 2.7.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.6.0",
3
+ "version": "2.7.0",
4
4
  "main": "./dist/index.js",
5
5
  "exports": {
6
6
  ".": {
@@ -50,5 +50,5 @@
50
50
  "postmark": "4.0.2",
51
51
  "stripe": "^16.6.0"
52
52
  },
53
- "gitHead": "7a3f9f6c08058dc8b671befbfad73184afdc6d7c"
53
+ "gitHead": "319a4de9fac39a31110cddbfa9a580d1b9c8f730"
54
54
  }
@@ -23,7 +23,7 @@ const filterCompilers: SQLFilterDefinitions = {
23
23
  startDate: createSQLColumnFilterCompiler('startDate'),
24
24
  endDate: createSQLColumnFilterCompiler('endDate'),
25
25
  groupIds: createSQLExpressionFilterCompiler(
26
- SQL.jsonValue(SQL.column('meta'), '$.value.groupIds'),
26
+ SQL.jsonValue(SQL.column('meta'), '$.value.groups[*].id'),
27
27
  undefined,
28
28
  true,
29
29
  true
@@ -35,7 +35,10 @@ const filterCompilers: SQLFilterDefinitions = {
35
35
  true
36
36
  ),
37
37
  organizationTagIds: createSQLExpressionFilterCompiler(
38
- SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds')
38
+ SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds'),
39
+ undefined,
40
+ true,
41
+ true
39
42
  )
40
43
  }
41
44
 
@@ -3,8 +3,8 @@ import { Decoder } from '@simonbackx/simple-encoding';
3
3
  import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
4
4
  import { SimpleError } from '@simonbackx/simple-errors';
5
5
  import { Email, Member, MemberWithRegistrations, Platform } from '@stamhoofd/models';
6
- import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLJSONValue, SQLOrderBy, SQLOrderByDirection, SQLScalar, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler, joinSQLQuery } from "@stamhoofd/sql";
7
- import { CountFilteredRequest, EmailRecipientFilterType, GroupStatus, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, getSortFilter, mergeFilters } from '@stamhoofd/structures';
6
+ import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLOrderBy, SQLOrderByDirection, SQLScalar, SQLSortDefinitions, baseSQLFilterCompilers, compileToSQLFilter, compileToSQLSorter, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler, joinSQLQuery } from "@stamhoofd/sql";
7
+ import { CountFilteredRequest, EmailRecipientFilterType, LimitedFilteredRequest, MembersBlob, PaginatedResponse, PermissionLevel, StamhoofdFilter, getSortFilter, mergeFilters } from '@stamhoofd/structures';
8
8
  import { DataValidator, Formatter } from '@stamhoofd/utility';
9
9
 
10
10
  import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
@@ -36,6 +36,7 @@ Email.recipientLoaders.set(EmailRecipientFilterType.Members, {
36
36
  return await q.count();
37
37
  }
38
38
  });
39
+
39
40
  Email.recipientLoaders.set(EmailRecipientFilterType.MemberParents, {
40
41
  fetch: async (query: LimitedFilteredRequest) => {
41
42
  const result = await GetMembersEndpoint.buildData(query)
@@ -54,6 +55,24 @@ Email.recipientLoaders.set(EmailRecipientFilterType.MemberParents, {
54
55
  }
55
56
  });
56
57
 
58
+ Email.recipientLoaders.set(EmailRecipientFilterType.MemberUnverified, {
59
+ fetch: async (query: LimitedFilteredRequest) => {
60
+ const result = await GetMembersEndpoint.buildData(query)
61
+
62
+ return new PaginatedResponse({
63
+ results: result.results.members.flatMap(m => m.getEmailRecipients(['unverified'])),
64
+ next: result.next
65
+ });
66
+ },
67
+
68
+ count: async (query: LimitedFilteredRequest) => {
69
+ const q = await GetMembersEndpoint.buildQuery(query)
70
+ return await q.sum(
71
+ SQL.jsonLength(SQL.column('details'), '$.value.unverifiedEmails')
72
+ );
73
+ }
74
+ });
75
+
57
76
  const registrationFilterCompilers: SQLFilterDefinitions = {
58
77
  ...baseSQLFilterCompilers,
59
78
  "price": createSQLColumnFilterCompiler('price'),
@@ -378,9 +397,6 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
378
397
  }
379
398
  },
380
399
  periodId: platform.periodId,
381
- registeredAt: {
382
- $neq: null
383
- },
384
400
  group: {
385
401
  defaultAgeGroupId: {
386
402
  $neq: null
@@ -397,16 +413,26 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
397
413
  const groups = await Context.auth.getAccessibleGroups(organization.id)
398
414
 
399
415
  if (groups === 'all') {
400
- scopeFilter = {
401
- registrations: {
402
- $elemMatch: {
403
- organizationId: organization.id,
404
- registeredAt: {
405
- $neq: null
416
+ if (await Context.auth.hasFullAccess(organization.id)) {
417
+ // Can access full history for now
418
+ scopeFilter = {
419
+ registrations: {
420
+ $elemMatch: {
421
+ organizationId: organization.id,
406
422
  }
407
423
  }
408
- }
409
- };
424
+ };
425
+ } else {
426
+ // Can only access current period
427
+ scopeFilter = {
428
+ registrations: {
429
+ $elemMatch: {
430
+ organizationId: organization.id,
431
+ periodId: organization.periodId,
432
+ }
433
+ }
434
+ };
435
+ }
410
436
  } else {
411
437
  scopeFilter = {
412
438
  registrations: {
@@ -415,9 +441,6 @@ export class GetMembersEndpoint extends Endpoint<Params, Query, Body, ResponseBo
415
441
  periodId: organization.periodId,
416
442
  groupId: {
417
443
  $in: groups
418
- },
419
- registeredAt: {
420
- $neq: null
421
444
  }
422
445
  }
423
446
  }
@@ -13,7 +13,6 @@ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructure
13
13
  import { BuckarooHelper } from '../../../helpers/BuckarooHelper';
14
14
  import { Context } from '../../../helpers/Context';
15
15
  import { StripeHelper } from '../../../helpers/StripeHelper';
16
- import { ExchangePaymentEndpoint } from '../../organization/shared/ExchangePaymentEndpoint';
17
16
  type Params = Record<string, never>;
18
17
  type Query = undefined;
19
18
  type Body = IDRegisterCheckout
@@ -424,7 +423,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
424
423
  balanceItem.status = shouldMarkValid ? BalanceItemStatus.Pending : BalanceItemStatus.Hidden
425
424
  await balanceItem.save();
426
425
 
427
- items.push(balanceItem)
426
+ items.push(balanceItem);
428
427
  }
429
428
 
430
429
  if (checkout.cart.balanceItems.length && whoWillPayNow === 'nobody') {
@@ -471,9 +470,7 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
471
470
  await BalanceItem.deleteForDeletedRegistration(existingRegistration.id)
472
471
 
473
472
  // Clear the registration
474
- existingRegistration.deactivatedAt = new Date()
475
- await existingRegistration.save()
476
- existingRegistration.scheduleStockUpdate()
473
+ await existingRegistration.deactivate()
477
474
 
478
475
  const group = groups.find(g => g.id === existingRegistration.groupId)
479
476
  if (!group) {
@@ -916,6 +916,10 @@ export class AdminPermissionChecker {
916
916
  const isUserManager = this.isUserManager(member)
917
917
  if (isUserManager) {
918
918
  // For the user manager, we don't delete data, because when registering a new member, it doesn't have any organizations yet...
919
+
920
+ // Notes are not visible for the member.
921
+ data.details.notes = null;
922
+
919
923
  return data;
920
924
  }
921
925
 
@@ -955,48 +959,63 @@ export class AdminPermissionChecker {
955
959
  })
956
960
  }
957
961
 
958
- if (data.details.recordAnswers) {
959
- if (!(data.details.recordAnswers instanceof PatchMap)) {
960
- throw new SimpleError({
961
- code: 'invalid_request',
962
- message: 'Cannot PUT recordAnswers',
963
- statusCode: 400
964
- })
965
- }
966
- const isUserManager = this.isUserManager(member)
967
- const records = isUserManager ? new Set() : await this.getAccessibleRecordSet(member, PermissionLevel.Write)
962
+ const hasRecordAnswers = !!data.details.recordAnswers;
963
+ const hasNotes = data.details.notes !== undefined;
968
964
 
969
- for (const [key, value] of data.details.recordAnswers.entries()) {
970
- let name: string | undefined = undefined
971
- if (value) {
972
- if (value.isPatch()) {
973
- throw new SimpleError({
974
- code: 'invalid_request',
975
- message: 'Cannot PATCH a record answer object',
976
- statusCode: 400
977
- })
978
- }
965
+ if(hasRecordAnswers || hasNotes) {
966
+ const isUserManager = this.isUserManager(member);
979
967
 
980
- const id = value.settings.id
968
+ if (hasRecordAnswers) {
969
+ if (!(data.details.recordAnswers instanceof PatchMap)) {
970
+ throw new SimpleError({
971
+ code: 'invalid_request',
972
+ message: 'Cannot PUT recordAnswers',
973
+ statusCode: 400
974
+ })
975
+ }
976
+
977
+ const records = isUserManager ? new Set() : await this.getAccessibleRecordSet(member, PermissionLevel.Write)
978
+
979
+ for (const [key, value] of data.details.recordAnswers.entries()) {
980
+ let name: string | undefined = undefined
981
+ if (value) {
982
+ if (value.isPatch()) {
983
+ throw new SimpleError({
984
+ code: 'invalid_request',
985
+ message: 'Cannot PATCH a record answer object',
986
+ statusCode: 400
987
+ })
988
+ }
989
+
990
+ const id = value.settings.id
991
+
992
+ if (id !== key) {
993
+ throw new SimpleError({
994
+ code: 'invalid_request',
995
+ message: 'Record answer key does not match record id',
996
+ statusCode: 400
997
+ })
998
+ }
999
+
1000
+ name = value.settings.name
1001
+ }
981
1002
 
982
- if (id !== key) {
1003
+ if (!isUserManager && !records.has(key)) {
983
1004
  throw new SimpleError({
984
- code: 'invalid_request',
985
- message: 'Record answer key does not match record id',
1005
+ code: 'permission_denied',
1006
+ message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
986
1007
  statusCode: 400
987
1008
  })
988
1009
  }
989
-
990
- name = value.settings.name
991
1010
  }
1011
+ }
992
1012
 
993
- if (!isUserManager && !records.has(key)) {
994
- throw new SimpleError({
995
- code: 'permission_denied',
996
- message: `Je hebt geen toegangsrechten om het antwoord op ${name ?? 'deze vraag'} aan te passen voor dit lid`,
997
- statusCode: 400
998
- })
999
- }
1013
+ if(hasNotes && isUserManager) {
1014
+ throw new SimpleError({
1015
+ code: 'permission_denied',
1016
+ message: 'Cannot edit notes',
1017
+ statusCode: 400
1018
+ })
1000
1019
  }
1001
1020
  }
1002
1021
 
@@ -15,8 +15,8 @@ export class MemberUserSyncerStatic {
15
15
  userEmails.push(member.details.email)
16
16
  }
17
17
 
18
- const uncategorizedEmails: string[] = member.details.uncategorizedEmails;
19
- const parentAndUncategorizedEmails = member.details.parentsHaveAccess ? member.details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails).concat(uncategorizedEmails) : []
18
+ const unverifiedEmails: string[] = member.details.unverifiedEmails;
19
+ const parentAndUnverifiedEmails = member.details.parentsHaveAccess ? member.details.parents.flatMap(p => p.email ? [p.email, ...p.alternativeEmails] : p.alternativeEmails).concat(unverifiedEmails) : []
20
20
 
21
21
  // Make sure all these users have access to the member
22
22
  for (const email of userEmails) {
@@ -24,14 +24,14 @@ export class MemberUserSyncerStatic {
24
24
  await this.linkUser(email, member, false)
25
25
  }
26
26
 
27
- for (const email of parentAndUncategorizedEmails) {
28
- // Link parents and uncategorized emails
27
+ for (const email of parentAndUnverifiedEmails) {
28
+ // Link parents and unverified emails
29
29
  await this.linkUser(email, member, true)
30
30
  }
31
31
 
32
32
  // Remove access of users that are not in this list
33
33
  for (const user of member.users) {
34
- if (!userEmails.includes(user.email) && !parentAndUncategorizedEmails.includes(user.email)) {
34
+ if (!userEmails.includes(user.email) && !parentAndUnverifiedEmails.includes(user.email)) {
35
35
  await this.unlinkUser(user, member)
36
36
  }
37
37
  }