@stamhoofd/backend 2.78.2 → 2.78.4
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/.env.ci.json +32 -16
- package/index.ts +7 -0
- package/jest.config.cjs +17 -0
- package/package.json +10 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +0 -10
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.test.ts +726 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +31 -18
- package/src/endpoints/global/members/shouldCheckIfMemberIsDuplicate.ts +9 -21
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +1 -1
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +0 -4
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +0 -4
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.test.ts +288 -8
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +8 -13
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +7 -7
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +2 -217
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +2 -2
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +6 -3
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +4 -6
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +2 -20
- package/src/helpers/AdminPermissionChecker.ts +88 -140
- package/src/helpers/GlobalHelper.ts +6 -1
- package/src/services/FileSignService.ts +3 -3
- package/src/services/MemberRecordStore.ts +155 -0
- package/src/services/PlatformMembershipService.ts +17 -8
- package/tests/e2e/register.test.ts +49 -21
- package/tests/helpers/StripeMocker.ts +7 -2
- package/tests/jest.global.setup.ts +6 -1
- package/tests/jest.setup.ts +10 -2
|
@@ -16,8 +16,7 @@ import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
|
16
16
|
import { SetupStepUpdater } from '../../../helpers/SetupStepUpdater';
|
|
17
17
|
import { PlatformMembershipService } from '../../../services/PlatformMembershipService';
|
|
18
18
|
import { RegistrationService } from '../../../services/RegistrationService';
|
|
19
|
-
import { shouldCheckIfMemberIsDuplicateForPatch
|
|
20
|
-
import { AuditLogService } from '../../../services/AuditLogService';
|
|
19
|
+
import { shouldCheckIfMemberIsDuplicateForPatch } from './shouldCheckIfMemberIsDuplicate';
|
|
21
20
|
|
|
22
21
|
type Params = Record<string, never>;
|
|
23
22
|
type Query = undefined;
|
|
@@ -109,12 +108,10 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
109
108
|
struct.details.cleanData();
|
|
110
109
|
member.details = struct.details;
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (duplicate) {
|
|
111
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, struct.details.securityCode, 'put');
|
|
112
|
+
if (duplicate) {
|
|
115
113
|
// Merge data
|
|
116
|
-
|
|
117
|
-
}
|
|
114
|
+
member = duplicate;
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
// We risk creating a new member without being able to access it manually afterwards
|
|
@@ -159,12 +156,11 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
159
156
|
const securityCode = patch.details?.securityCode; // will get cleared after the filter
|
|
160
157
|
|
|
161
158
|
if (!member) {
|
|
162
|
-
throw Context.auth.
|
|
159
|
+
throw Context.auth.memberNotFoundOrNoAccess();
|
|
163
160
|
}
|
|
164
161
|
|
|
165
|
-
if (!await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
166
|
-
|
|
167
|
-
await PatchOrganizationMembersEndpoint.checkSecurityCode(member, securityCode);
|
|
162
|
+
if (!(await Context.auth.canAccessMember(member, PermissionLevel.Write))) {
|
|
163
|
+
await PatchOrganizationMembersEndpoint.checkSecurityCode(member, securityCode, 'patch');
|
|
168
164
|
}
|
|
169
165
|
|
|
170
166
|
patch = await Context.auth.filterMemberPatch(member, patch);
|
|
@@ -194,7 +190,7 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
194
190
|
}
|
|
195
191
|
|
|
196
192
|
if (shouldCheckDuplicate) {
|
|
197
|
-
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, securityCode);
|
|
193
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, securityCode, 'patch');
|
|
198
194
|
|
|
199
195
|
if (duplicate) {
|
|
200
196
|
// Remove the member from the list
|
|
@@ -759,8 +755,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
759
755
|
}
|
|
760
756
|
}
|
|
761
757
|
|
|
762
|
-
static async checkSecurityCode(member: MemberWithRegistrations, securityCode: string | null | undefined) {
|
|
763
|
-
if (await member.isSafeToMergeDuplicateWithoutSecurityCode() || await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
758
|
+
static async checkSecurityCode(member: MemberWithRegistrations, securityCode: string | null | undefined, type: 'put' | 'patch') {
|
|
759
|
+
if ((type === 'put' && await member.isSafeToMergeDuplicateWithoutSecurityCode()) || await Context.auth.canAccessMember(member, PermissionLevel.Write)) {
|
|
764
760
|
console.log('checkSecurityCode: without security code: allowed for ' + member.id);
|
|
765
761
|
}
|
|
766
762
|
else if (securityCode) {
|
|
@@ -802,8 +798,8 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
802
798
|
const log = new AuditLog();
|
|
803
799
|
|
|
804
800
|
// a member has multiple organizations, so this is difficult to determine - for now it is only visible in the admin panel
|
|
805
|
-
log.organizationId = member.organizationId;
|
|
806
|
-
|
|
801
|
+
log.organizationId = member.organizationId;
|
|
802
|
+
|
|
807
803
|
log.type = AuditLogType.MemberSecurityCodeUsed;
|
|
808
804
|
log.source = AuditLogSource.Anonymous;
|
|
809
805
|
|
|
@@ -823,6 +819,9 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
823
819
|
await log.save();
|
|
824
820
|
}
|
|
825
821
|
else {
|
|
822
|
+
if (type === 'patch') {
|
|
823
|
+
throw Context.auth.memberNotFoundOrNoAccess();
|
|
824
|
+
}
|
|
826
825
|
throw new SimpleError({
|
|
827
826
|
code: 'known_member_missing_rights',
|
|
828
827
|
message: 'Creating known member without sufficient access rights',
|
|
@@ -832,11 +831,25 @@ export class PatchOrganizationMembersEndpoint extends Endpoint<Params, Query, Bo
|
|
|
832
831
|
}
|
|
833
832
|
}
|
|
834
833
|
|
|
835
|
-
static
|
|
834
|
+
static shouldCheckIfMemberIsDuplicate(put: Member): boolean {
|
|
835
|
+
if (put.details.firstName.length <= 3 && put.details.lastName.length <= 3) {
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const age = put.details.age;
|
|
840
|
+
// do not check if member is duplicate for historical members
|
|
841
|
+
return age !== null && age < 81;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
static async checkDuplicate(member: Member, securityCode: string | null | undefined, type: 'put' | 'patch') {
|
|
845
|
+
if (!this.shouldCheckIfMemberIsDuplicate(member)) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
836
849
|
// Check for duplicates and prevent creating a duplicate member by a user
|
|
837
850
|
const duplicate = await this.findExistingMember(member);
|
|
838
851
|
if (duplicate) {
|
|
839
|
-
await this.checkSecurityCode(duplicate, securityCode);
|
|
852
|
+
await this.checkSecurityCode(duplicate, securityCode, type);
|
|
840
853
|
|
|
841
854
|
// Merge data
|
|
842
855
|
// NOTE: We use mergeTwoMembers instead of mergeMultipleMembers, because we should never safe 'member' , because that one does not exist in the database
|
|
@@ -1,34 +1,22 @@
|
|
|
1
1
|
import { AutoEncoderPatchType } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { MemberDetails, MemberWithRegistrationsBlob } from '@stamhoofd/structures';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Returns true when either the firstname, lastname or birthday has changed
|
|
6
|
+
*/
|
|
4
7
|
export function shouldCheckIfMemberIsDuplicateForPatch(patch: { details: MemberDetails | AutoEncoderPatchType<MemberDetails> | undefined }, originalDetails: MemberDetails): boolean {
|
|
5
8
|
if (patch.details === undefined) {
|
|
6
9
|
return false;
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
((patch.details.firstName !== undefined && patch.details.firstName.length > 3) || (patch.details.firstName === undefined && originalDetails.firstName.length > 3))
|
|
12
|
-
// or has long last name
|
|
13
|
-
|| ((patch.details.lastName !== undefined && patch.details.lastName.length > 3) || (patch.details.lastName === undefined && originalDetails.lastName.length > 3))
|
|
14
|
-
)
|
|
15
|
-
// has name change or birthday change
|
|
16
|
-
&& (
|
|
17
|
-
// has first name change
|
|
12
|
+
// name or birthday has changed
|
|
13
|
+
if (
|
|
18
14
|
(patch.details.firstName !== undefined && patch.details.firstName !== originalDetails.firstName)
|
|
19
|
-
// has last name change
|
|
20
15
|
|| (patch.details.lastName !== undefined && patch.details.lastName !== originalDetails.lastName)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function shouldCheckIfMemberIsDuplicateForPut(put: MemberWithRegistrationsBlob): boolean {
|
|
27
|
-
if (put.details.firstName.length <= 3 && put.details.lastName.length <= 3) {
|
|
28
|
-
return false;
|
|
16
|
+
|| (patch.details.birthDay !== undefined && patch.details.birthDay !== originalDetails.birthDay)
|
|
17
|
+
) {
|
|
18
|
+
return true;
|
|
29
19
|
}
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
// do not check if member is duplicate for historical members
|
|
33
|
-
return age !== null && age < 81;
|
|
21
|
+
return false;
|
|
34
22
|
}
|
|
@@ -4,7 +4,7 @@ import { Address, Country, CreateOrganization, NewUser, Organization as Organiza
|
|
|
4
4
|
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
5
5
|
import { CreateOrganizationEndpoint } from './CreateOrganizationEndpoint';
|
|
6
6
|
|
|
7
|
-
describe('Endpoint.CreateOrganization', () => {
|
|
7
|
+
describe.skip('Endpoint.CreateOrganization', () => {
|
|
8
8
|
// Test endpoint
|
|
9
9
|
const endpoint = new CreateOrganizationEndpoint();
|
|
10
10
|
|
|
@@ -11,7 +11,6 @@ describe('Endpoint.GetOrganizationFromDomain', () => {
|
|
|
11
11
|
|
|
12
12
|
test('Get organization from default uri', async () => {
|
|
13
13
|
const organization = await new OrganizationFactory({}).create();
|
|
14
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
15
14
|
|
|
16
15
|
const r = Request.buildJson('GET', '/v2/organization-from-domain');
|
|
17
16
|
r.query = {
|
|
@@ -26,12 +25,10 @@ describe('Endpoint.GetOrganizationFromDomain', () => {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
expect(response.body.id).toEqual(organization.id);
|
|
29
|
-
expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort());
|
|
30
28
|
});
|
|
31
29
|
|
|
32
30
|
test('Get organization from custom domain', async () => {
|
|
33
31
|
const organization = await new OrganizationFactory({ domain: 'inschrijven.mijnscouts.be' }).create();
|
|
34
|
-
const groups = await new GroupFactory({ organization }).createMultiple(2);
|
|
35
32
|
|
|
36
33
|
const r = Request.buildJson('GET', '/v2/organization-from-domain');
|
|
37
34
|
r.query = {
|
|
@@ -46,6 +43,5 @@ describe('Endpoint.GetOrganizationFromDomain', () => {
|
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
expect(response.body.id).toEqual(organization.id);
|
|
49
|
-
expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort());
|
|
50
46
|
});
|
|
51
47
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
2
|
import { OrganizationFactory } from '@stamhoofd/models';
|
|
3
|
-
import { OrganizationSimple } from '@stamhoofd/structures';
|
|
4
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
4
|
|
|
6
5
|
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
@@ -25,7 +24,6 @@ describe('Endpoint.SearchOrganization', () => {
|
|
|
25
24
|
expect(response.body).toHaveLength(1);
|
|
26
25
|
|
|
27
26
|
// Access token should be expired
|
|
28
|
-
expect(response.body[0]).toBeInstanceOf(OrganizationSimple);
|
|
29
27
|
expect(response.status).toEqual(200);
|
|
30
28
|
expect(response.body[0]).toMatchObject({
|
|
31
29
|
id: organization.id,
|
|
@@ -50,8 +48,6 @@ describe('Endpoint.SearchOrganization', () => {
|
|
|
50
48
|
expect(response.body).toHaveLength(2);
|
|
51
49
|
|
|
52
50
|
// Access token should be expired
|
|
53
|
-
expect(response.body[0]).toBeInstanceOf(OrganizationSimple);
|
|
54
|
-
expect(response.body[1]).toBeInstanceOf(OrganizationSimple);
|
|
55
51
|
expect(response.status).toEqual(200);
|
|
56
52
|
expect(response.body.map(o => o.id).sort()).toEqual(organizations.map(o => o.id).sort());
|
|
57
53
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { PatchableArray } from '@simonbackx/simple-encoding';
|
|
1
|
+
import { PatchableArray, PatchMap } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { Endpoint, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
-
import { GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
4
|
-
import { MemberDetails, MemberWithRegistrationsBlob, Parent } from '@stamhoofd/structures';
|
|
3
|
+
import { GroupFactory, MemberFactory, OrganizationFactory, Platform, RegistrationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
4
|
+
import { MemberDetails, MemberWithRegistrationsBlob, OrganizationMetaData, OrganizationRecordsConfiguration, Parent, PatchAnswers, PermissionLevel, RecordCategory, RecordSettings, RecordTextAnswer } from '@stamhoofd/structures';
|
|
5
5
|
import { testServer } from '../../../../tests/helpers/TestServer';
|
|
6
6
|
import { PatchUserMembersEndpoint } from './PatchUserMembersEndpoint';
|
|
7
|
+
import { Database } from '@simonbackx/simple-database';
|
|
8
|
+
import { TestUtils } from '@stamhoofd/test-utils';
|
|
7
9
|
|
|
8
10
|
const baseUrl = `/members`;
|
|
9
11
|
const endpoint = new PatchUserMembersEndpoint();
|
|
@@ -17,12 +19,20 @@ const birthDay = { year: 1993, month: 4, day: 5 };
|
|
|
17
19
|
const errorWithCode = (code: string) => expect.objectContaining({ code }) as jest.Constructable;
|
|
18
20
|
|
|
19
21
|
describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(async () => {
|
|
27
|
+
// Delete all members (so the duplicate checks work as expected)
|
|
28
|
+
await Database.delete('DELETE FROM `members`');
|
|
29
|
+
});
|
|
30
|
+
|
|
20
31
|
describe('Duplicate members', () => {
|
|
21
32
|
test('The security code should be a requirement', async () => {
|
|
22
33
|
const organization = await new OrganizationFactory({ }).create();
|
|
23
|
-
const user = await new UserFactory({
|
|
34
|
+
const user = await new UserFactory({ }).create();
|
|
24
35
|
const existingMember = await new MemberFactory({
|
|
25
|
-
organization,
|
|
26
36
|
firstName,
|
|
27
37
|
lastName,
|
|
28
38
|
birthDay,
|
|
@@ -50,9 +60,8 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
50
60
|
|
|
51
61
|
test('The security code is not a requirement for members without additional data', async () => {
|
|
52
62
|
const organization = await new OrganizationFactory({ }).create();
|
|
53
|
-
const user = await new UserFactory({
|
|
63
|
+
const user = await new UserFactory({ }).create();
|
|
54
64
|
const existingMember = await new MemberFactory({
|
|
55
|
-
organization,
|
|
56
65
|
firstName,
|
|
57
66
|
lastName,
|
|
58
67
|
birthDay,
|
|
@@ -108,7 +117,6 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
108
117
|
});
|
|
109
118
|
|
|
110
119
|
const existingMember = await new MemberFactory({
|
|
111
|
-
organization,
|
|
112
120
|
birthDay,
|
|
113
121
|
details,
|
|
114
122
|
}).create();
|
|
@@ -160,4 +168,276 @@ describe('Endpoint.PatchUserMembersEndpoint', () => {
|
|
|
160
168
|
expect(member.details.parents[0]).toEqual(existingMember.details.parents[0]);
|
|
161
169
|
});
|
|
162
170
|
});
|
|
171
|
+
|
|
172
|
+
describe('Record answers', () => {
|
|
173
|
+
test('A user can save answers of records of an organization it has not yet registered for', async () => {
|
|
174
|
+
const commentsRecord = RecordSettings.create({
|
|
175
|
+
name: 'Opmerkingen',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const recordCategory = RecordCategory.create({
|
|
179
|
+
name: 'Medische fiche',
|
|
180
|
+
records: [
|
|
181
|
+
commentsRecord,
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
const organization = await new OrganizationFactory({
|
|
185
|
+
meta: OrganizationMetaData.create({
|
|
186
|
+
recordsConfiguration: OrganizationRecordsConfiguration.create({
|
|
187
|
+
recordCategories: [recordCategory],
|
|
188
|
+
}),
|
|
189
|
+
}),
|
|
190
|
+
}).create();
|
|
191
|
+
|
|
192
|
+
const user = await new UserFactory({ }).create();
|
|
193
|
+
const existingMember = await new MemberFactory({
|
|
194
|
+
firstName,
|
|
195
|
+
lastName,
|
|
196
|
+
birthDay,
|
|
197
|
+
generateData: false,
|
|
198
|
+
// Give user access to this member
|
|
199
|
+
user,
|
|
200
|
+
}).create();
|
|
201
|
+
|
|
202
|
+
const token = await Token.createToken(user);
|
|
203
|
+
|
|
204
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
205
|
+
|
|
206
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
207
|
+
settings: commentsRecord,
|
|
208
|
+
value: 'Some comments',
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
const arr: Body = new PatchableArray();
|
|
212
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
213
|
+
id: existingMember.id,
|
|
214
|
+
details: MemberDetails.patch({
|
|
215
|
+
recordAnswers,
|
|
216
|
+
}),
|
|
217
|
+
});
|
|
218
|
+
arr.addPatch(patch);
|
|
219
|
+
|
|
220
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
221
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
222
|
+
const response = await testServer.test(endpoint, request);
|
|
223
|
+
|
|
224
|
+
// Check returned
|
|
225
|
+
expect(response.status).toBe(200);
|
|
226
|
+
expect(response.body.members.length).toBe(1);
|
|
227
|
+
const member = response.body.members[0];
|
|
228
|
+
expect(member.details.recordAnswers.get(commentsRecord.id)).toMatchObject({
|
|
229
|
+
value: 'Some comments',
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('A user cannot save answers to organization read-only records', async () => {
|
|
234
|
+
const commentsRecord = RecordSettings.create({
|
|
235
|
+
name: 'Opmerkingen',
|
|
236
|
+
externalPermissionLevel: PermissionLevel.Read,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const recordCategory = RecordCategory.create({
|
|
240
|
+
name: 'Medische fiche',
|
|
241
|
+
records: [
|
|
242
|
+
commentsRecord,
|
|
243
|
+
],
|
|
244
|
+
});
|
|
245
|
+
const organization = await new OrganizationFactory({
|
|
246
|
+
meta: OrganizationMetaData.create({
|
|
247
|
+
recordsConfiguration: OrganizationRecordsConfiguration.create({
|
|
248
|
+
recordCategories: [recordCategory],
|
|
249
|
+
}),
|
|
250
|
+
}),
|
|
251
|
+
}).create();
|
|
252
|
+
|
|
253
|
+
const user = await new UserFactory({ }).create();
|
|
254
|
+
const existingMember = await new MemberFactory({
|
|
255
|
+
firstName,
|
|
256
|
+
lastName,
|
|
257
|
+
birthDay,
|
|
258
|
+
generateData: false,
|
|
259
|
+
// Give user access to this member
|
|
260
|
+
user,
|
|
261
|
+
}).create();
|
|
262
|
+
|
|
263
|
+
const token = await Token.createToken(user);
|
|
264
|
+
|
|
265
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
266
|
+
|
|
267
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
268
|
+
settings: commentsRecord,
|
|
269
|
+
value: 'Some comments',
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
const arr: Body = new PatchableArray();
|
|
273
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
274
|
+
id: existingMember.id,
|
|
275
|
+
details: MemberDetails.patch({
|
|
276
|
+
recordAnswers,
|
|
277
|
+
}),
|
|
278
|
+
});
|
|
279
|
+
arr.addPatch(patch);
|
|
280
|
+
|
|
281
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
282
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
283
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('A user can save answers of records of the platform', async () => {
|
|
287
|
+
const commentsRecord = RecordSettings.create({
|
|
288
|
+
name: 'Opmerkingen',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const recordCategory = RecordCategory.create({
|
|
292
|
+
name: 'Medische fiche',
|
|
293
|
+
records: [
|
|
294
|
+
commentsRecord,
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const platform = await Platform.getShared();
|
|
299
|
+
platform.config.recordsConfiguration.recordCategories.push(recordCategory);
|
|
300
|
+
await platform.save();
|
|
301
|
+
|
|
302
|
+
const organization = await new OrganizationFactory({}).create();
|
|
303
|
+
|
|
304
|
+
const user = await new UserFactory({ }).create();
|
|
305
|
+
const existingMember = await new MemberFactory({
|
|
306
|
+
firstName,
|
|
307
|
+
lastName,
|
|
308
|
+
birthDay,
|
|
309
|
+
generateData: false,
|
|
310
|
+
// Give user access to this member
|
|
311
|
+
user,
|
|
312
|
+
}).create();
|
|
313
|
+
|
|
314
|
+
const token = await Token.createToken(user);
|
|
315
|
+
|
|
316
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
317
|
+
|
|
318
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
319
|
+
settings: commentsRecord,
|
|
320
|
+
value: 'Some comments',
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
const arr: Body = new PatchableArray();
|
|
324
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
325
|
+
id: existingMember.id,
|
|
326
|
+
details: MemberDetails.patch({
|
|
327
|
+
recordAnswers,
|
|
328
|
+
}),
|
|
329
|
+
});
|
|
330
|
+
arr.addPatch(patch);
|
|
331
|
+
|
|
332
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
333
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
334
|
+
const response = await testServer.test(endpoint, request);
|
|
335
|
+
|
|
336
|
+
// Check returned
|
|
337
|
+
expect(response.status).toBe(200);
|
|
338
|
+
expect(response.body.members.length).toBe(1);
|
|
339
|
+
const member = response.body.members[0];
|
|
340
|
+
expect(member.details.recordAnswers.get(commentsRecord.id)).toMatchObject({
|
|
341
|
+
value: 'Some comments',
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('A user cannot save answers to platform read-only records', async () => {
|
|
346
|
+
const commentsRecord = RecordSettings.create({
|
|
347
|
+
name: 'Opmerkingen',
|
|
348
|
+
externalPermissionLevel: PermissionLevel.Read,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const recordCategory = RecordCategory.create({
|
|
352
|
+
name: 'Medische fiche',
|
|
353
|
+
records: [
|
|
354
|
+
commentsRecord,
|
|
355
|
+
],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const platform = await Platform.getShared();
|
|
359
|
+
platform.config.recordsConfiguration.recordCategories.push(recordCategory);
|
|
360
|
+
await platform.save();
|
|
361
|
+
|
|
362
|
+
const organization = await new OrganizationFactory({}).create();
|
|
363
|
+
|
|
364
|
+
const user = await new UserFactory({ }).create();
|
|
365
|
+
const existingMember = await new MemberFactory({
|
|
366
|
+
firstName,
|
|
367
|
+
lastName,
|
|
368
|
+
birthDay,
|
|
369
|
+
generateData: false,
|
|
370
|
+
// Give user access to this member
|
|
371
|
+
user,
|
|
372
|
+
}).create();
|
|
373
|
+
|
|
374
|
+
const token = await Token.createToken(user);
|
|
375
|
+
|
|
376
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
377
|
+
|
|
378
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
379
|
+
settings: commentsRecord,
|
|
380
|
+
value: 'Some comments',
|
|
381
|
+
}));
|
|
382
|
+
|
|
383
|
+
const arr: Body = new PatchableArray();
|
|
384
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
385
|
+
id: existingMember.id,
|
|
386
|
+
details: MemberDetails.patch({
|
|
387
|
+
recordAnswers,
|
|
388
|
+
}),
|
|
389
|
+
});
|
|
390
|
+
arr.addPatch(patch);
|
|
391
|
+
|
|
392
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
393
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
394
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test('A user can not save anwers to inexisting records', async () => {
|
|
398
|
+
const commentsRecord = RecordSettings.create({
|
|
399
|
+
name: 'Opmerkingen',
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const organization = await new OrganizationFactory({
|
|
403
|
+
meta: OrganizationMetaData.create({
|
|
404
|
+
recordsConfiguration: OrganizationRecordsConfiguration.create({
|
|
405
|
+
recordCategories: [],
|
|
406
|
+
}),
|
|
407
|
+
}),
|
|
408
|
+
}).create();
|
|
409
|
+
|
|
410
|
+
const user = await new UserFactory({ }).create();
|
|
411
|
+
const existingMember = await new MemberFactory({
|
|
412
|
+
firstName,
|
|
413
|
+
lastName,
|
|
414
|
+
birthDay,
|
|
415
|
+
generateData: false,
|
|
416
|
+
// Give user access to this member
|
|
417
|
+
user,
|
|
418
|
+
}).create();
|
|
419
|
+
|
|
420
|
+
const token = await Token.createToken(user);
|
|
421
|
+
|
|
422
|
+
const recordAnswers = new PatchMap() as PatchAnswers;
|
|
423
|
+
|
|
424
|
+
recordAnswers.set(commentsRecord.id, RecordTextAnswer.create({
|
|
425
|
+
settings: commentsRecord,
|
|
426
|
+
value: 'Some comments',
|
|
427
|
+
}));
|
|
428
|
+
|
|
429
|
+
const arr: Body = new PatchableArray();
|
|
430
|
+
const patch = MemberWithRegistrationsBlob.patch({
|
|
431
|
+
id: existingMember.id,
|
|
432
|
+
details: MemberDetails.patch({
|
|
433
|
+
recordAnswers,
|
|
434
|
+
}),
|
|
435
|
+
});
|
|
436
|
+
arr.addPatch(patch);
|
|
437
|
+
|
|
438
|
+
const request = Request.buildJson('PATCH', baseUrl, organization.getApiHost(), arr);
|
|
439
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
440
|
+
await expect(testServer.test(endpoint, request)).rejects.toThrow(errorWithCode('permission_denied'));
|
|
441
|
+
});
|
|
442
|
+
});
|
|
163
443
|
});
|
|
@@ -8,7 +8,7 @@ import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructure
|
|
|
8
8
|
import { Context } from '../../../helpers/Context';
|
|
9
9
|
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer';
|
|
10
10
|
import { PatchOrganizationMembersEndpoint } from '../../global/members/PatchOrganizationMembersEndpoint';
|
|
11
|
-
import { shouldCheckIfMemberIsDuplicateForPatch
|
|
11
|
+
import { shouldCheckIfMemberIsDuplicateForPatch } from '../members/shouldCheckIfMemberIsDuplicate';
|
|
12
12
|
type Params = Record<string, never>;
|
|
13
13
|
type Query = undefined;
|
|
14
14
|
type Body = PatchableArrayAutoEncoder<MemberWithRegistrationsBlob>;
|
|
@@ -61,12 +61,10 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
61
61
|
|
|
62
62
|
this.throwIfInvalidDetails(member.details);
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
64
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, struct.details.securityCode, 'put');
|
|
65
|
+
if (duplicate) {
|
|
66
|
+
addedMembers.push(duplicate);
|
|
67
|
+
continue;
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
await member.save();
|
|
@@ -79,12 +77,9 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
79
77
|
for (let struct of request.body.getPatches()) {
|
|
80
78
|
const member = members.find(m => m.id === struct.id);
|
|
81
79
|
if (!member) {
|
|
82
|
-
throw
|
|
83
|
-
code: 'invalid_member',
|
|
84
|
-
message: "This member does not exist or you don't have permissions to modify this member",
|
|
85
|
-
human: 'Je probeert een lid aan te passen die niet (meer) bestaat. Er ging ergens iets mis.',
|
|
86
|
-
});
|
|
80
|
+
throw Context.auth.memberNotFoundOrNoAccess();
|
|
87
81
|
}
|
|
82
|
+
|
|
88
83
|
const securityCode = struct.details?.securityCode; // will get cleared after the filter
|
|
89
84
|
struct = await Context.auth.filterMemberPatch(member, struct);
|
|
90
85
|
|
|
@@ -117,7 +112,7 @@ export class PatchUserMembersEndpoint extends Endpoint<Params, Query, Body, Resp
|
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
if (shouldCheckDuplicate) {
|
|
120
|
-
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, securityCode);
|
|
115
|
+
const duplicate = await PatchOrganizationMembersEndpoint.checkDuplicate(member, securityCode, 'patch');
|
|
121
116
|
if (duplicate) {
|
|
122
117
|
// Remove the member from the list
|
|
123
118
|
members.splice(members.findIndex(m => m.id === member.id), 1);
|
|
@@ -770,7 +770,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
770
770
|
administrationFee: 0,
|
|
771
771
|
freeContribution: 0,
|
|
772
772
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
773
|
-
totalPrice:
|
|
773
|
+
totalPrice: 25,
|
|
774
774
|
asOrganizationId: organization.id,
|
|
775
775
|
});
|
|
776
776
|
|
|
@@ -1649,7 +1649,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
1649
1649
|
administrationFee: 0,
|
|
1650
1650
|
freeContribution: 0,
|
|
1651
1651
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
1652
|
-
totalPrice:
|
|
1652
|
+
totalPrice: 30,
|
|
1653
1653
|
asOrganizationId: organization.id,
|
|
1654
1654
|
customer: null,
|
|
1655
1655
|
});
|
|
@@ -1927,7 +1927,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
1927
1927
|
administrationFee: 0,
|
|
1928
1928
|
freeContribution: 0,
|
|
1929
1929
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
1930
|
-
totalPrice:
|
|
1930
|
+
totalPrice: 30,
|
|
1931
1931
|
asOrganizationId: organization1.id,
|
|
1932
1932
|
customer: null,
|
|
1933
1933
|
});
|
|
@@ -2049,7 +2049,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
2049
2049
|
administrationFee: 0,
|
|
2050
2050
|
freeContribution: 0,
|
|
2051
2051
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
2052
|
-
totalPrice:
|
|
2052
|
+
totalPrice: 30,
|
|
2053
2053
|
asOrganizationId: organization.id,
|
|
2054
2054
|
customer: null,
|
|
2055
2055
|
});
|
|
@@ -2226,7 +2226,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
2226
2226
|
administrationFee: 0,
|
|
2227
2227
|
freeContribution: 0,
|
|
2228
2228
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
2229
|
-
totalPrice:
|
|
2229
|
+
totalPrice: 30,
|
|
2230
2230
|
customer: null,
|
|
2231
2231
|
asOrganizationId: organization.id,
|
|
2232
2232
|
});
|
|
@@ -2331,7 +2331,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
2331
2331
|
administrationFee: 0,
|
|
2332
2332
|
freeContribution: 0,
|
|
2333
2333
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
2334
|
-
totalPrice:
|
|
2334
|
+
totalPrice: 30,
|
|
2335
2335
|
customer: null,
|
|
2336
2336
|
asOrganizationId: organization.id,
|
|
2337
2337
|
});
|
|
@@ -2355,7 +2355,7 @@ describe('Endpoint.RegisterMembers', () => {
|
|
|
2355
2355
|
administrationFee: 0,
|
|
2356
2356
|
freeContribution: 0,
|
|
2357
2357
|
paymentMethod: PaymentMethod.PointOfSale,
|
|
2358
|
-
totalPrice:
|
|
2358
|
+
totalPrice: 30,
|
|
2359
2359
|
customer: null,
|
|
2360
2360
|
asOrganizationId: organization.id,
|
|
2361
2361
|
});
|