@stamhoofd/backend 2.120.6 → 2.121.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 +12 -12
- package/src/audit-logs/RegistrationInvitationLogger.ts +46 -0
- package/src/audit-logs/init.ts +2 -0
- package/src/crons/index.ts +2 -0
- package/src/crons/invoices.ts +166 -0
- package/src/crons/mollie-chargebacks.ts +87 -0
- package/src/crons.ts +47 -10
- package/src/email-recipient-loaders/payments.ts +84 -41
- package/src/endpoints/global/groups/GetGroupsCountEndpoint.ts +51 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +22 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +4 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts +45 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts +495 -0
- package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.ts +216 -0
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts +405 -0
- package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.ts +168 -0
- package/src/endpoints/organization/dashboard/balance-items/PatchBalanceItemsEndpoint.ts +15 -0
- package/src/endpoints/{global → organization/dashboard}/billing/DeactivatePackageEndpoint.ts +3 -4
- package/src/endpoints/organization/dashboard/billing/DeleteOrganizationMandateEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceCollectionEndpoint.ts +56 -0
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedPayableBalanceEndpoint.ts +42 -19
- package/src/endpoints/organization/dashboard/billing/GetOrganizationMandatesEndpoint.ts +64 -0
- package/src/endpoints/organization/dashboard/billing/GetPackagesEndpoint.ts +11 -3
- package/src/endpoints/organization/dashboard/billing/OrganizationCheckoutEndpoint.ts +308 -0
- package/src/endpoints/organization/dashboard/billing/PatchOrganizationMandatesEndpoint.ts +94 -0
- package/src/endpoints/organization/dashboard/invoices/GetInvoicesEndpoint.ts +7 -0
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +5 -4
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +7 -2
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +17 -8
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +3 -3
- package/src/endpoints/organization/dashboard/receivable-balances/ChargeReceivableBalancesEndpoint.ts +127 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +13 -4
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +7 -1
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +1 -1
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +13 -11
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +14 -19
- package/src/helpers/AdminPermissionChecker.ts +11 -3
- package/src/helpers/AuthenticatedStructures.ts +94 -6
- package/src/helpers/FinancialSupportHelper.ts +21 -0
- package/src/helpers/RecordAnswerHelper.test.ts +746 -0
- package/src/helpers/RecordAnswerHelper.ts +116 -0
- package/src/helpers/StripeHelper.ts +2 -3
- package/src/helpers/ViesHelper.ts +7 -3
- package/src/seeds/1750090030-records-configuration.ts +68 -3
- package/src/seeds/1752848561-groups-registration-periods.ts +26 -2
- package/src/seeds/1779121239-default-invoice-email-template.sql +3 -0
- package/src/services/BalanceItemService.ts +12 -16
- package/src/services/InvoiceService.ts +372 -72
- package/src/services/MollieService.ts +537 -0
- package/src/services/PaymentMandateService.ts +214 -0
- package/src/services/PaymentService.ts +578 -222
- package/src/services/PlatformMembershipService.ts +1 -1
- package/src/services/RegistrationService.ts +66 -5
- package/src/services/STPackageService.ts +0 -7
- package/src/services/data/invoice.hbs.html +686 -0
- package/src/sql-filters/groups.ts +11 -1
- package/src/sql-filters/payments.ts +5 -0
- package/src/sql-filters/registration-invitations.ts +90 -0
- package/src/sql-sorters/registration-invitations.ts +36 -0
- package/vitest.config.js +1 -0
- package/src/endpoints/global/billing/ActivatePackagesEndpoint.ts +0 -216
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
|
|
5
|
+
|
|
6
|
+
import { Context } from '../../../helpers/Context.js';
|
|
7
|
+
import { GetGroupsEndpoint } from './GetGroupsEndpoint.js';
|
|
8
|
+
|
|
9
|
+
type Params = Record<string, never>;
|
|
10
|
+
type Query = CountFilteredRequest;
|
|
11
|
+
type Body = undefined;
|
|
12
|
+
type ResponseBody = CountResponse;
|
|
13
|
+
|
|
14
|
+
export class GetGroupsCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>;
|
|
16
|
+
|
|
17
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
18
|
+
if (request.method !== 'GET') {
|
|
19
|
+
return [false];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const params = Endpoint.parseParameters(request.url, '/groups/count', {});
|
|
23
|
+
|
|
24
|
+
if (params) {
|
|
25
|
+
return [true, params as Params];
|
|
26
|
+
}
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
31
|
+
const organization = await Context.setOptionalOrganizationScope();
|
|
32
|
+
await Context.optionalAuthenticate();
|
|
33
|
+
|
|
34
|
+
if (!organization) {
|
|
35
|
+
if (!Context.auth.hasSomePlatformAccess()) {
|
|
36
|
+
throw Context.auth.error();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const query = await GetGroupsEndpoint.buildQuery(request.query);
|
|
41
|
+
|
|
42
|
+
const count = await query
|
|
43
|
+
.count();
|
|
44
|
+
|
|
45
|
+
return new Response(
|
|
46
|
+
CountResponse.create({
|
|
47
|
+
count,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { AutoEncoderPatchType, Decoder} from '@simonbackx/simple-encoding';
|
|
1
|
+
import type { AutoEncoderPatchType, Decoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { isPatchableArray, patchObject } from '@simonbackx/simple-encoding';
|
|
3
|
-
import type { DecodedRequest, Request} from '@simonbackx/simple-endpoints';
|
|
3
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
4
4
|
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
5
5
|
import { Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
6
|
-
import type { MemberResponsibility, PlatformConfig, PlatformPremiseType} from '@stamhoofd/structures';
|
|
6
|
+
import type { EventNotificationType, MemberResponsibility, PlatformConfig, PlatformPremiseType } from '@stamhoofd/structures';
|
|
7
7
|
import { Platform as PlatformStruct } from '@stamhoofd/structures';
|
|
8
8
|
|
|
9
9
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
@@ -12,6 +12,7 @@ import { Context } from '../../../helpers/Context.js';
|
|
|
12
12
|
import { MembershipCharger } from '../../../helpers/MembershipCharger.js';
|
|
13
13
|
import { MemberUserSyncer } from '../../../helpers/MemberUserSyncer.js';
|
|
14
14
|
import { PeriodHelper } from '../../../helpers/PeriodHelper.js';
|
|
15
|
+
import { RecordAnswerHelper } from '../../../helpers/RecordAnswerHelper.js';
|
|
15
16
|
import { SetupStepUpdater } from '../../../helpers/SetupStepUpdater.js';
|
|
16
17
|
import { TagHelper } from '../../../helpers/TagHelper.js';
|
|
17
18
|
import { PlatformMembershipService } from '../../../services/PlatformMembershipService.js';
|
|
@@ -95,6 +96,24 @@ export class PatchPlatformEndpoint extends Endpoint<
|
|
|
95
96
|
|
|
96
97
|
if (request.body.config.organizationLevelRecordsConfiguration) {
|
|
97
98
|
shouldUpdateSetupSteps = true;
|
|
99
|
+
|
|
100
|
+
RecordAnswerHelper.throwIfPatchOrPutIsInvalid(
|
|
101
|
+
platform.config.organizationLevelRecordsConfiguration.recordCategories,
|
|
102
|
+
request.body.config.organizationLevelRecordsConfiguration.recordCategories);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (request.body.config.recordsConfiguration) {
|
|
106
|
+
RecordAnswerHelper.throwIfPatchOrPutIsInvalid(
|
|
107
|
+
platform.config.recordsConfiguration.recordCategories,
|
|
108
|
+
request.body.config.recordsConfiguration.recordCategories);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (request.body.config.eventNotificationTypes) {
|
|
112
|
+
RecordAnswerHelper.throwIfPatchOrPutsAreInvalid<'recordCategories', string, EventNotificationType, AutoEncoderPatchType<EventNotificationType>>(
|
|
113
|
+
platform.config.eventNotificationTypes,
|
|
114
|
+
request.body.config.eventNotificationTypes,
|
|
115
|
+
'recordCategories',
|
|
116
|
+
)
|
|
98
117
|
}
|
|
99
118
|
|
|
100
119
|
const newConfig = request.body.config;
|
|
@@ -841,6 +841,10 @@ export class RegisterMembersEndpoint extends Endpoint<Params, Query, Body, Respo
|
|
|
841
841
|
members,
|
|
842
842
|
serviceFeeType: 'members',
|
|
843
843
|
payingOrganization,
|
|
844
|
+
createMandate: null,
|
|
845
|
+
useMandate: null,
|
|
846
|
+
paymentConfiguration: organization.meta.registrationPaymentConfiguration,
|
|
847
|
+
privatePaymentConfiguration: organization.privateMeta.registrationPaymentConfiguration
|
|
844
848
|
});
|
|
845
849
|
|
|
846
850
|
if (response) {
|
package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsCountEndpoint.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import type { DecodedRequest, Request } from '@simonbackx/simple-endpoints';
|
|
3
|
+
import { Endpoint, Response } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import { CountFilteredRequest, CountResponse } from '@stamhoofd/structures';
|
|
5
|
+
|
|
6
|
+
import { Context } from '../../../helpers/Context.js';
|
|
7
|
+
import { GetRegistrationInvitationsEndpoint } from './GetRegistrationInvitationsEndpoint.js';
|
|
8
|
+
|
|
9
|
+
type Params = Record<string, never>;
|
|
10
|
+
type Query = CountFilteredRequest;
|
|
11
|
+
type Body = undefined;
|
|
12
|
+
type ResponseBody = CountResponse;
|
|
13
|
+
|
|
14
|
+
export class GetRegistrationInvitationsCountEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
queryDecoder = CountFilteredRequest as Decoder<CountFilteredRequest>;
|
|
16
|
+
|
|
17
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
18
|
+
if (request.method !== 'GET') {
|
|
19
|
+
return [false];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const params = Endpoint.parseParameters(request.url, '/registration-invitations/count', {});
|
|
23
|
+
|
|
24
|
+
if (params) {
|
|
25
|
+
return [true, params as Params];
|
|
26
|
+
}
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
31
|
+
await Context.setOptionalOrganizationScope();
|
|
32
|
+
await Context.authenticate();
|
|
33
|
+
|
|
34
|
+
const query = await GetRegistrationInvitationsEndpoint.buildQuery(request.query);
|
|
35
|
+
|
|
36
|
+
const count = await query
|
|
37
|
+
.count();
|
|
38
|
+
|
|
39
|
+
return new Response(
|
|
40
|
+
CountResponse.create({
|
|
41
|
+
count,
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/endpoints/global/registration-invitations/GetRegistrationInvitationsEndpoint.test.ts
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
2
|
+
import type { Organization, User } from '@stamhoofd/models';
|
|
3
|
+
import { GroupFactory, MemberFactory, OrganizationFactory, RegistrationInvitation, RegistrationInvitationFactory, RegistrationPeriodFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
4
|
+
import type { PaginatedResponse, RegistrationInvitation as RegistrationInvitationStruct, StamhoofdFilter } from '@stamhoofd/structures';
|
|
5
|
+
import { LimitedFilteredRequest, PermissionLevel, Permissions, PermissionsResourceType, ResourcePermissions, TranslatedString } from '@stamhoofd/structures';
|
|
6
|
+
import { TestUtils } from '@stamhoofd/test-utils';
|
|
7
|
+
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
8
|
+
import { GetRegistrationInvitationsEndpoint } from './GetRegistrationInvitationsEndpoint.js';
|
|
9
|
+
|
|
10
|
+
describe('Endpoint.GetRegistrationInvitationsEndpoint', () => {
|
|
11
|
+
const endpoint = new GetRegistrationInvitationsEndpoint();
|
|
12
|
+
|
|
13
|
+
const getInvitations = async ({ filter, organization, user }: { filter: StamhoofdFilter; organization: Organization | null; user: User | null }) => {
|
|
14
|
+
let authorization = '';
|
|
15
|
+
|
|
16
|
+
if (user) {
|
|
17
|
+
const token = await Token.createToken(user);
|
|
18
|
+
authorization = 'Bearer ' + token.accessToken;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const request = Request.get({
|
|
22
|
+
path: '/registration-invitations',
|
|
23
|
+
host: organization?.getApiHost(),
|
|
24
|
+
query: new LimitedFilteredRequest({
|
|
25
|
+
filter,
|
|
26
|
+
limit: 10,
|
|
27
|
+
}),
|
|
28
|
+
headers: {
|
|
29
|
+
authorization,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return testServer.test<PaginatedResponse<RegistrationInvitationStruct[], LimitedFilteredRequest>>(endpoint, request)
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
await RegistrationInvitation.delete();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('Permission checking', () => {
|
|
41
|
+
describe('read access for specific group', () => {
|
|
42
|
+
test('should only return invitations for that group', async () => {
|
|
43
|
+
const organization = await new OrganizationFactory({}).create();
|
|
44
|
+
const groupName = 'test groep';
|
|
45
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
46
|
+
const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
47
|
+
|
|
48
|
+
const resources = new Map();
|
|
49
|
+
|
|
50
|
+
resources.set(
|
|
51
|
+
PermissionsResourceType.Groups, new Map([[
|
|
52
|
+
group.id,
|
|
53
|
+
ResourcePermissions.create({
|
|
54
|
+
level: PermissionLevel.Write,
|
|
55
|
+
}),
|
|
56
|
+
]]),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const user = await new UserFactory({
|
|
60
|
+
organization,
|
|
61
|
+
permissions: Permissions.create({
|
|
62
|
+
level: PermissionLevel.None,
|
|
63
|
+
resources,
|
|
64
|
+
}),
|
|
65
|
+
}).create();
|
|
66
|
+
|
|
67
|
+
const member = await new MemberFactory({
|
|
68
|
+
organization, user,
|
|
69
|
+
firstName: 'John',
|
|
70
|
+
lastName: 'Doe',
|
|
71
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
72
|
+
}).create();
|
|
73
|
+
|
|
74
|
+
const member2 = await new MemberFactory({
|
|
75
|
+
organization, user,
|
|
76
|
+
firstName: 'Test1',
|
|
77
|
+
lastName: 'Test1',
|
|
78
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
79
|
+
}).create();
|
|
80
|
+
|
|
81
|
+
// invitations for group
|
|
82
|
+
await new RegistrationInvitationFactory({ group, member, organization }).create();
|
|
83
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
|
|
84
|
+
|
|
85
|
+
// invitations for other group
|
|
86
|
+
const invitationForOtherGroup = await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
|
|
87
|
+
|
|
88
|
+
const response = await getInvitations({
|
|
89
|
+
filter: null,
|
|
90
|
+
organization,
|
|
91
|
+
user,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// assert
|
|
95
|
+
expect(response.status).toBe(200);
|
|
96
|
+
expect(response.body.results).toHaveLength(2);
|
|
97
|
+
expect(response.body.results.map((r) => r.id)).not.toContain(invitationForOtherGroup.id);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('cannot read invitations for other group', async () => {
|
|
101
|
+
const organization = await new OrganizationFactory({}).create();
|
|
102
|
+
const groupName = 'test groep';
|
|
103
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
104
|
+
const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
105
|
+
|
|
106
|
+
const resources = new Map();
|
|
107
|
+
|
|
108
|
+
resources.set(
|
|
109
|
+
PermissionsResourceType.Groups, new Map([[
|
|
110
|
+
group.id,
|
|
111
|
+
ResourcePermissions.create({
|
|
112
|
+
level: PermissionLevel.Write,
|
|
113
|
+
}),
|
|
114
|
+
]]),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const user = await new UserFactory({
|
|
118
|
+
organization,
|
|
119
|
+
permissions: Permissions.create({
|
|
120
|
+
level: PermissionLevel.None,
|
|
121
|
+
resources,
|
|
122
|
+
}),
|
|
123
|
+
}).create();
|
|
124
|
+
|
|
125
|
+
const member = await new MemberFactory({
|
|
126
|
+
organization, user,
|
|
127
|
+
firstName: 'John',
|
|
128
|
+
lastName: 'Doe',
|
|
129
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
130
|
+
}).create();
|
|
131
|
+
|
|
132
|
+
const member2 = await new MemberFactory({
|
|
133
|
+
organization, user,
|
|
134
|
+
firstName: 'Test1',
|
|
135
|
+
lastName: 'Test1',
|
|
136
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
137
|
+
}).create();
|
|
138
|
+
|
|
139
|
+
// invitations for group
|
|
140
|
+
await new RegistrationInvitationFactory({ group, member, organization }).create();
|
|
141
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
|
|
142
|
+
|
|
143
|
+
// invitations for other group
|
|
144
|
+
await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
|
|
145
|
+
|
|
146
|
+
// assert
|
|
147
|
+
await expect(getInvitations({
|
|
148
|
+
filter: {
|
|
149
|
+
groupId: group2.id
|
|
150
|
+
},
|
|
151
|
+
organization,
|
|
152
|
+
user,
|
|
153
|
+
})).rejects.toThrow('You do not have access to this group');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('read access for specific organization', () => {
|
|
158
|
+
test('can read invitations for any group of organization', async () => {
|
|
159
|
+
const organization = await new OrganizationFactory({}).create();
|
|
160
|
+
const groupName = 'test groep';
|
|
161
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
162
|
+
const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
163
|
+
|
|
164
|
+
const user = await new UserFactory({
|
|
165
|
+
organization,
|
|
166
|
+
permissions: Permissions.create({ level: PermissionLevel.Read }),
|
|
167
|
+
}).create();
|
|
168
|
+
|
|
169
|
+
const member = await new MemberFactory({
|
|
170
|
+
organization, user,
|
|
171
|
+
firstName: 'John',
|
|
172
|
+
lastName: 'Doe',
|
|
173
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
174
|
+
}).create();
|
|
175
|
+
|
|
176
|
+
const member2 = await new MemberFactory({
|
|
177
|
+
organization, user,
|
|
178
|
+
firstName: 'Test1',
|
|
179
|
+
lastName: 'Test1',
|
|
180
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
181
|
+
}).create();
|
|
182
|
+
|
|
183
|
+
const organization2 = await new OrganizationFactory({}).create();
|
|
184
|
+
const groupOfOtherOrganization = await new GroupFactory({ organization: organization2 }).create();
|
|
185
|
+
|
|
186
|
+
// invitations for group
|
|
187
|
+
await new RegistrationInvitationFactory({ group, member, organization }).create();
|
|
188
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
|
|
189
|
+
|
|
190
|
+
// invitations for other group
|
|
191
|
+
await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
|
|
192
|
+
|
|
193
|
+
// invitations for other organization
|
|
194
|
+
const invitationForOtherOrganization = await new RegistrationInvitationFactory({
|
|
195
|
+
group: groupOfOtherOrganization,
|
|
196
|
+
member,
|
|
197
|
+
organization: organization2
|
|
198
|
+
}).create();
|
|
199
|
+
|
|
200
|
+
const response = await getInvitations({
|
|
201
|
+
filter: null,
|
|
202
|
+
organization,
|
|
203
|
+
user,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// assert
|
|
207
|
+
expect(response.status).toBe(200);
|
|
208
|
+
// should only include registrations for organization of user
|
|
209
|
+
expect(response.body.results).toHaveLength(3);
|
|
210
|
+
expect(response.body.results.map((r) => r.id)).not.toContain(invitationForOtherOrganization.id);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('cannot read invitations for other organization', async () => {
|
|
214
|
+
const organization = await new OrganizationFactory({}).create();
|
|
215
|
+
const groupName = 'test groep';
|
|
216
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
217
|
+
const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
218
|
+
|
|
219
|
+
const user = await new UserFactory({
|
|
220
|
+
organization,
|
|
221
|
+
permissions: Permissions.create({ level: PermissionLevel.Read }),
|
|
222
|
+
}).create();
|
|
223
|
+
|
|
224
|
+
const member = await new MemberFactory({
|
|
225
|
+
organization, user,
|
|
226
|
+
firstName: 'John',
|
|
227
|
+
lastName: 'Doe',
|
|
228
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
229
|
+
}).create();
|
|
230
|
+
|
|
231
|
+
const member2 = await new MemberFactory({
|
|
232
|
+
organization, user,
|
|
233
|
+
firstName: 'Test1',
|
|
234
|
+
lastName: 'Test1',
|
|
235
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
236
|
+
}).create();
|
|
237
|
+
|
|
238
|
+
const organization2 = await new OrganizationFactory({}).create();
|
|
239
|
+
const groupOfOtherOrganization = await new GroupFactory({ organization: organization2 }).create();
|
|
240
|
+
|
|
241
|
+
// invitations for group
|
|
242
|
+
await new RegistrationInvitationFactory({ group, member, organization }).create();
|
|
243
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
|
|
244
|
+
|
|
245
|
+
// invitations for other group
|
|
246
|
+
await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
|
|
247
|
+
|
|
248
|
+
// invitations for other organization
|
|
249
|
+
await new RegistrationInvitationFactory({
|
|
250
|
+
group: groupOfOtherOrganization,
|
|
251
|
+
member,
|
|
252
|
+
organization: organization2
|
|
253
|
+
}).create();
|
|
254
|
+
|
|
255
|
+
const response = await getInvitations({
|
|
256
|
+
filter: {
|
|
257
|
+
organizationId: organization2.id
|
|
258
|
+
},
|
|
259
|
+
organization,
|
|
260
|
+
user,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// assert
|
|
264
|
+
expect(response.status).toBe(200);
|
|
265
|
+
expect(response.body.results).toHaveLength(0);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('platform permissions', () => {
|
|
270
|
+
beforeEach(async () => {
|
|
271
|
+
TestUtils.setEnvironment('userMode', 'platform');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('can read all invitations for read access for platform', async () => {
|
|
275
|
+
const organization = await new OrganizationFactory({}).create();
|
|
276
|
+
const groupName = 'test groep';
|
|
277
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
278
|
+
const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
279
|
+
|
|
280
|
+
const user = await new UserFactory({
|
|
281
|
+
globalPermissions: Permissions.create({ level: PermissionLevel.Read }),
|
|
282
|
+
}).create();
|
|
283
|
+
|
|
284
|
+
const member = await new MemberFactory({
|
|
285
|
+
organization, user,
|
|
286
|
+
firstName: 'John',
|
|
287
|
+
lastName: 'Doe',
|
|
288
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
289
|
+
}).create();
|
|
290
|
+
|
|
291
|
+
const member2 = await new MemberFactory({
|
|
292
|
+
organization, user,
|
|
293
|
+
firstName: 'Test1',
|
|
294
|
+
lastName: 'Test1',
|
|
295
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
296
|
+
}).create();
|
|
297
|
+
|
|
298
|
+
const organization2 = await new OrganizationFactory({}).create();
|
|
299
|
+
const groupOfOtherOrganization = await new GroupFactory({ organization: organization2 }).create();
|
|
300
|
+
|
|
301
|
+
// invitations for group
|
|
302
|
+
await new RegistrationInvitationFactory({ group, member, organization }).create();
|
|
303
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
|
|
304
|
+
|
|
305
|
+
// invitations for other group
|
|
306
|
+
await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
|
|
307
|
+
|
|
308
|
+
// invitations for other organization
|
|
309
|
+
const invitationForOtherOrganization = await new RegistrationInvitationFactory({
|
|
310
|
+
group: groupOfOtherOrganization,
|
|
311
|
+
member,
|
|
312
|
+
organization: organization2
|
|
313
|
+
}).create();
|
|
314
|
+
|
|
315
|
+
const response = await getInvitations({
|
|
316
|
+
filter: null,
|
|
317
|
+
organization,
|
|
318
|
+
user,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// assert
|
|
322
|
+
expect(response.status).toBe(200);
|
|
323
|
+
// should only include registrations for organization of user
|
|
324
|
+
expect(response.body.results).toHaveLength(4);
|
|
325
|
+
expect(response.body.results.map((r) => r.id)).toContain(invitationForOtherOrganization.id);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('can only read invitations for group with tag if only access to groups with tags', async () => {
|
|
329
|
+
const organization1 = await new OrganizationFactory({}).create();
|
|
330
|
+
const period = await new RegistrationPeriodFactory({
|
|
331
|
+
organization: organization1,
|
|
332
|
+
startDate: new Date(2023, 0, 1),
|
|
333
|
+
endDate: new Date(2023, 11, 31),
|
|
334
|
+
}).create();
|
|
335
|
+
|
|
336
|
+
organization1.periodId = period.id;
|
|
337
|
+
await organization1.save();
|
|
338
|
+
|
|
339
|
+
const groupName = 'test groep';
|
|
340
|
+
const group = await new GroupFactory({ organization: organization1, name: new TranslatedString(groupName), period }).create();
|
|
341
|
+
const group2 = await new GroupFactory({ organization: organization1, name: new TranslatedString(groupName), period }).create();
|
|
342
|
+
|
|
343
|
+
const resources = new Map();
|
|
344
|
+
|
|
345
|
+
const tag = 'tagtest';
|
|
346
|
+
|
|
347
|
+
resources.set(
|
|
348
|
+
PermissionsResourceType.OrganizationTags, new Map([[
|
|
349
|
+
tag,
|
|
350
|
+
ResourcePermissions.create({
|
|
351
|
+
level: PermissionLevel.Full,
|
|
352
|
+
accessRights: [],
|
|
353
|
+
}),
|
|
354
|
+
]]),
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const user = await new UserFactory({
|
|
358
|
+
globalPermissions: Permissions.create({
|
|
359
|
+
level: PermissionLevel.None,
|
|
360
|
+
roles: [],
|
|
361
|
+
resources,
|
|
362
|
+
}),
|
|
363
|
+
}).create();
|
|
364
|
+
|
|
365
|
+
const member = await new MemberFactory({
|
|
366
|
+
organization: organization1, user,
|
|
367
|
+
firstName: 'John',
|
|
368
|
+
lastName: 'Doe',
|
|
369
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
370
|
+
}).create();
|
|
371
|
+
|
|
372
|
+
const member2 = await new MemberFactory({
|
|
373
|
+
organization: organization1, user,
|
|
374
|
+
firstName: 'Test1',
|
|
375
|
+
lastName: 'Test1',
|
|
376
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
377
|
+
}).create();
|
|
378
|
+
|
|
379
|
+
const organization2 = await new OrganizationFactory({tags: [tag], period}).create();
|
|
380
|
+
const groupOfOtherOrganization = await new GroupFactory({ organization: organization2, period }).create();
|
|
381
|
+
|
|
382
|
+
// invitations for group
|
|
383
|
+
await new RegistrationInvitationFactory({ group, member, organization: organization1 }).create();
|
|
384
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization: organization1 }).create();
|
|
385
|
+
|
|
386
|
+
// invitations for other group
|
|
387
|
+
await new RegistrationInvitationFactory({ group: group2, member, organization: organization1 }).create();
|
|
388
|
+
|
|
389
|
+
// invitations for other organization
|
|
390
|
+
const invitationForOtherOrganization = await new RegistrationInvitationFactory({
|
|
391
|
+
group: groupOfOtherOrganization,
|
|
392
|
+
member,
|
|
393
|
+
organization: organization2
|
|
394
|
+
}).create();
|
|
395
|
+
|
|
396
|
+
const response = await getInvitations({
|
|
397
|
+
filter: null,
|
|
398
|
+
// organization scope is required
|
|
399
|
+
organization: organization2,
|
|
400
|
+
user,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// assert
|
|
404
|
+
expect(response.status).toBe(200);
|
|
405
|
+
// should only include registrations for organization of user
|
|
406
|
+
expect(response.body.results).toHaveLength(1);
|
|
407
|
+
expect(response.body.results.map((r) => r.id)).toContain(invitationForOtherOrganization.id);
|
|
408
|
+
|
|
409
|
+
// should not have access to all invitations of organization1
|
|
410
|
+
await expect(getInvitations({
|
|
411
|
+
filter: null,
|
|
412
|
+
// organization scope is required
|
|
413
|
+
organization: organization1,
|
|
414
|
+
user,
|
|
415
|
+
})).rejects.toThrow('You must filter on a group of the organization you are trying to access');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('cannot get invitations if platform scope and no access to all organization tags', async () => {
|
|
419
|
+
const organization = await new OrganizationFactory({}).create();
|
|
420
|
+
const period = await new RegistrationPeriodFactory({
|
|
421
|
+
organization,
|
|
422
|
+
startDate: new Date(2023, 0, 1),
|
|
423
|
+
endDate: new Date(2023, 11, 31),
|
|
424
|
+
}).create();
|
|
425
|
+
|
|
426
|
+
organization.periodId = period.id;
|
|
427
|
+
await organization.save();
|
|
428
|
+
|
|
429
|
+
const groupName = 'test groep';
|
|
430
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName), period }).create();
|
|
431
|
+
const group2 = await new GroupFactory({ organization, name: new TranslatedString(groupName), period }).create();
|
|
432
|
+
|
|
433
|
+
const resources = new Map();
|
|
434
|
+
|
|
435
|
+
const tag = 'tagtest';
|
|
436
|
+
|
|
437
|
+
resources.set(
|
|
438
|
+
PermissionsResourceType.OrganizationTags, new Map([[
|
|
439
|
+
tag,
|
|
440
|
+
ResourcePermissions.create({
|
|
441
|
+
level: PermissionLevel.Full,
|
|
442
|
+
accessRights: [],
|
|
443
|
+
}),
|
|
444
|
+
]]),
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const user = await new UserFactory({
|
|
448
|
+
globalPermissions: Permissions.create({
|
|
449
|
+
level: PermissionLevel.None,
|
|
450
|
+
roles: [],
|
|
451
|
+
resources,
|
|
452
|
+
}),
|
|
453
|
+
}).create();
|
|
454
|
+
|
|
455
|
+
const member = await new MemberFactory({
|
|
456
|
+
organization, user,
|
|
457
|
+
firstName: 'John',
|
|
458
|
+
lastName: 'Doe',
|
|
459
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
460
|
+
}).create();
|
|
461
|
+
|
|
462
|
+
const member2 = await new MemberFactory({
|
|
463
|
+
organization, user,
|
|
464
|
+
firstName: 'Test1',
|
|
465
|
+
lastName: 'Test1',
|
|
466
|
+
birthDay: {year: 1998, month: 3, day: 1}
|
|
467
|
+
}).create();
|
|
468
|
+
|
|
469
|
+
const organization2 = await new OrganizationFactory({tags: [tag], period}).create();
|
|
470
|
+
const groupOfOtherOrganization = await new GroupFactory({ organization: organization2, period }).create();
|
|
471
|
+
|
|
472
|
+
// invitations for group
|
|
473
|
+
await new RegistrationInvitationFactory({ group, member, organization }).create();
|
|
474
|
+
await new RegistrationInvitationFactory({ group, member: member2, organization }).create();
|
|
475
|
+
|
|
476
|
+
// invitations for other group
|
|
477
|
+
await new RegistrationInvitationFactory({ group: group2, member, organization }).create();
|
|
478
|
+
|
|
479
|
+
// invitations for other organization
|
|
480
|
+
await new RegistrationInvitationFactory({
|
|
481
|
+
group: groupOfOtherOrganization,
|
|
482
|
+
member,
|
|
483
|
+
organization: organization2
|
|
484
|
+
}).create();
|
|
485
|
+
|
|
486
|
+
await expect(getInvitations({
|
|
487
|
+
filter: null,
|
|
488
|
+
organization: null,
|
|
489
|
+
user,
|
|
490
|
+
})).rejects.toThrow('You do not have permissions for this action');
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
});
|