@stamhoofd/backend 2.120.5 → 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,216 @@
|
|
|
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 { SimpleError } from '@simonbackx/simple-errors';
|
|
5
|
+
import { Group, Platform, RegistrationInvitation } from '@stamhoofd/models';
|
|
6
|
+
import type { SQLFilterDefinitions, SQLSortDefinitions } from '@stamhoofd/sql';
|
|
7
|
+
import { applySQLSorter, compileToSQLFilter } from '@stamhoofd/sql';
|
|
8
|
+
import type { CountFilteredRequest, RegistrationInvitation as RegistrationInvitationStruct, StamhoofdFilter } from '@stamhoofd/structures';
|
|
9
|
+
import { GroupType, LimitedFilteredRequest, PaginatedResponse, PermissionLevel, assertSort } from '@stamhoofd/structures';
|
|
10
|
+
|
|
11
|
+
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures.js';
|
|
12
|
+
import { Context } from '../../../helpers/Context.js';
|
|
13
|
+
import { LimitedFilteredRequestHelper } from '../../../helpers/LimitedFilteredRequestHelper.js';
|
|
14
|
+
import { registrationInvitationFilterCompilers } from '../../../sql-filters/registration-invitations.js';
|
|
15
|
+
import { registrationInvitationSorters } from '../../../sql-sorters/registration-invitations.js';
|
|
16
|
+
import { GetMembersEndpoint } from '../members/GetMembersEndpoint.js';
|
|
17
|
+
import { validateGroupFilter } from '../members/helpers/validateGroupFilter.js';
|
|
18
|
+
|
|
19
|
+
type Params = Record<string, never>;
|
|
20
|
+
type Query = LimitedFilteredRequest;
|
|
21
|
+
type Body = undefined;
|
|
22
|
+
type ResponseBody = PaginatedResponse<RegistrationInvitationStruct[], LimitedFilteredRequest>;
|
|
23
|
+
|
|
24
|
+
const sorters: SQLSortDefinitions<RegistrationInvitation> = registrationInvitationSorters;
|
|
25
|
+
const filterCompilers: SQLFilterDefinitions = registrationInvitationFilterCompilers;
|
|
26
|
+
|
|
27
|
+
export class GetRegistrationInvitationsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
28
|
+
queryDecoder = LimitedFilteredRequest as Decoder<LimitedFilteredRequest>;
|
|
29
|
+
|
|
30
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
31
|
+
if (request.method !== 'GET') {
|
|
32
|
+
return [false];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const params = Endpoint.parseParameters(request.url, '/registration-invitations', {});
|
|
36
|
+
|
|
37
|
+
if (params) {
|
|
38
|
+
return [true, params as Params];
|
|
39
|
+
}
|
|
40
|
+
return [false];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static async buildQuery(q: CountFilteredRequest | LimitedFilteredRequest, permissionLevel: PermissionLevel = PermissionLevel.Read) {
|
|
44
|
+
const organization = Context.organization;
|
|
45
|
+
let scopeFilter: StamhoofdFilter | undefined = undefined;
|
|
46
|
+
|
|
47
|
+
// First do a quick validation of the groups, so that prevents the backend from having to add a scope filter
|
|
48
|
+
if (!Context.auth.canAccessAllPlatformMembers(permissionLevel) && !await validateGroupFilter({ filter: q.filter, permissionLevel, key: null })) {
|
|
49
|
+
if (!organization) {
|
|
50
|
+
const tags = Context.auth.getPlatformAccessibleOrganizationTags(permissionLevel);
|
|
51
|
+
if (tags !== 'all' && tags.length === 0) {
|
|
52
|
+
throw Context.auth.error();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (tags !== 'all') {
|
|
56
|
+
const platform = await Platform.getShared();
|
|
57
|
+
|
|
58
|
+
// Add organization scope filter
|
|
59
|
+
scopeFilter = {
|
|
60
|
+
group: {
|
|
61
|
+
$elemMatch: {
|
|
62
|
+
periodId: platform.periodIdIfPlatform,
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
organization: {
|
|
66
|
+
$elemMatch: {
|
|
67
|
+
tags: {
|
|
68
|
+
$in: tags,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (organization) {
|
|
78
|
+
// Add organization scope filter
|
|
79
|
+
if (await Context.auth.canAccessAllMembers(organization.id, permissionLevel)) {
|
|
80
|
+
if (await Context.auth.hasFullAccess(organization.id, permissionLevel)) {
|
|
81
|
+
// Can access full history for now
|
|
82
|
+
scopeFilter = {
|
|
83
|
+
organizationId: organization.id,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Can only access current period
|
|
88
|
+
scopeFilter = {
|
|
89
|
+
organizationId: organization.id,
|
|
90
|
+
group: {
|
|
91
|
+
$elemMatch: {
|
|
92
|
+
periodId: organization.periodId,
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const groups = await Group.getAll(organization.id, organization.periodId, true, [GroupType.Membership, GroupType.EventRegistration]);
|
|
100
|
+
Context.auth.cacheGroups(groups);
|
|
101
|
+
const groupIds: string[] = [];
|
|
102
|
+
|
|
103
|
+
for (const group of groups) {
|
|
104
|
+
if (await Context.auth.canAccessGroup(group, permissionLevel)) {
|
|
105
|
+
groupIds.push(group.id);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (groupIds.length === 0) {
|
|
110
|
+
throw Context.auth.error({
|
|
111
|
+
message: 'You must filter on a group of the organization you are trying to access',
|
|
112
|
+
human: $t(`%15g`),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
scopeFilter = {
|
|
117
|
+
groupId: {
|
|
118
|
+
$in: groupIds,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const query = RegistrationInvitation.select().setMaxExecutionTime(15 * 1000);
|
|
126
|
+
|
|
127
|
+
if (scopeFilter) {
|
|
128
|
+
query.where(await compileToSQLFilter(scopeFilter, filterCompilers));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (q.filter) {
|
|
132
|
+
query.where(await compileToSQLFilter(q.filter, filterCompilers));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const memberSearchFilter = GetMembersEndpoint.buildSearchFilter(q.search);
|
|
136
|
+
|
|
137
|
+
if (memberSearchFilter) {
|
|
138
|
+
const searchFilter: StamhoofdFilter = {
|
|
139
|
+
member: {
|
|
140
|
+
$elemMatch: memberSearchFilter,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
query.where(await compileToSQLFilter(searchFilter, filterCompilers));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (q instanceof LimitedFilteredRequest) {
|
|
148
|
+
if (q.pageFilter) {
|
|
149
|
+
query.where(await compileToSQLFilter(q.pageFilter, filterCompilers));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
q.sort = assertSort(q.sort, [{ key: 'id' }]);
|
|
153
|
+
applySQLSorter(query, q.sort, sorters);
|
|
154
|
+
query.limit(q.limit);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return query;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static async buildData(requestQuery: LimitedFilteredRequest, permissionLevel = PermissionLevel.Read) {
|
|
161
|
+
const query = await GetRegistrationInvitationsEndpoint.buildQuery(requestQuery, permissionLevel);
|
|
162
|
+
let data: RegistrationInvitation[];
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
data = await query.fetch();
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
if (error.message.includes('ER_QUERY_TIMEOUT')) {
|
|
169
|
+
throw new SimpleError({
|
|
170
|
+
code: 'timeout',
|
|
171
|
+
message: 'Query took too long',
|
|
172
|
+
human: $t(`%Cv`),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const next = LimitedFilteredRequestHelper.fixInfiniteLoadingLoop({
|
|
179
|
+
request: requestQuery,
|
|
180
|
+
results: data,
|
|
181
|
+
sorters,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return new PaginatedResponse<RegistrationInvitationStruct[], LimitedFilteredRequest>({
|
|
185
|
+
results: await AuthenticatedStructures.registrationInvitations(data),
|
|
186
|
+
next,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
191
|
+
await Context.setOptionalOrganizationScope();
|
|
192
|
+
await Context.authenticate();
|
|
193
|
+
|
|
194
|
+
const maxLimit = Context.auth.hasSomePlatformAccess() ? 1000 : 100;
|
|
195
|
+
|
|
196
|
+
if (request.query.limit > maxLimit) {
|
|
197
|
+
throw new SimpleError({
|
|
198
|
+
code: 'invalid_field',
|
|
199
|
+
field: 'limit',
|
|
200
|
+
message: 'Limit can not be more than ' + maxLimit,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (request.query.limit < 1) {
|
|
205
|
+
throw new SimpleError({
|
|
206
|
+
code: 'invalid_field',
|
|
207
|
+
field: 'limit',
|
|
208
|
+
message: 'Limit can not be less than 1',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return new Response(
|
|
213
|
+
await GetRegistrationInvitationsEndpoint.buildData(request.query),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
package/src/endpoints/global/registration-invitations/PatchRegistrationInvitationsEndpoint.test.ts
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import type { PatchableArrayAutoEncoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { PatchableArray } from '@simonbackx/simple-encoding';
|
|
3
|
+
import { Request } from '@simonbackx/simple-endpoints';
|
|
4
|
+
import type { Organization, User } from '@stamhoofd/models';
|
|
5
|
+
import { GroupFactory, MemberFactory, OrganizationFactory, RegistrationFactory, RegistrationInvitationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
6
|
+
import type { RegistrationInvitation as RegistrationInvitationStruct } from '@stamhoofd/structures';
|
|
7
|
+
import { PermissionLevel, Permissions, PermissionsResourceType, RegistrationInvitationRequest, ResourcePermissions, TranslatedString } from '@stamhoofd/structures';
|
|
8
|
+
import { testServer } from '../../../../tests/helpers/TestServer.js';
|
|
9
|
+
import { PatchRegistrationInvitationsEndpoint } from './PatchRegistrationInvitationsEndpoint.js';
|
|
10
|
+
|
|
11
|
+
describe('Endpoint.PatchRegistrationInvitationsEndpoint', () => {
|
|
12
|
+
const endpoint = new PatchRegistrationInvitationsEndpoint();
|
|
13
|
+
|
|
14
|
+
const patchInvitations = async ({ patch, organization, user }: { patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest>; organization: Organization; user: User | null }) => {
|
|
15
|
+
const request = Request.buildJson('PATCH', '/registration-invitations', organization.getApiHost(), patch);
|
|
16
|
+
if (user) {
|
|
17
|
+
const token = await Token.createToken(user);
|
|
18
|
+
request.headers.authorization = 'Bearer ' + token.accessToken;
|
|
19
|
+
}
|
|
20
|
+
return await testServer.test<RegistrationInvitationStruct[]>(endpoint, request);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('Put invitation', () => {
|
|
24
|
+
test('Happy path', async () => {
|
|
25
|
+
const organization = await new OrganizationFactory({}).create();
|
|
26
|
+
const groupName = 'test groep';
|
|
27
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
28
|
+
const resources = new Map();
|
|
29
|
+
|
|
30
|
+
resources.set(
|
|
31
|
+
PermissionsResourceType.Groups, new Map([[
|
|
32
|
+
group.id,
|
|
33
|
+
ResourcePermissions.create({
|
|
34
|
+
level: PermissionLevel.Write,
|
|
35
|
+
}),
|
|
36
|
+
]]),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const user = await new UserFactory({
|
|
40
|
+
organization,
|
|
41
|
+
permissions: Permissions.create({
|
|
42
|
+
level: PermissionLevel.None,
|
|
43
|
+
resources,
|
|
44
|
+
}),
|
|
45
|
+
}).create();
|
|
46
|
+
|
|
47
|
+
const member = await new MemberFactory({
|
|
48
|
+
organization, user,
|
|
49
|
+
firstName: 'John',
|
|
50
|
+
lastName: 'Doe',
|
|
51
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
52
|
+
}).create();
|
|
53
|
+
|
|
54
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
55
|
+
|
|
56
|
+
patch.addPut(RegistrationInvitationRequest.create({
|
|
57
|
+
groupId: group.id,
|
|
58
|
+
memberId: member.id,
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
const response = await patchInvitations({
|
|
62
|
+
patch,
|
|
63
|
+
organization,
|
|
64
|
+
user,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// assert
|
|
68
|
+
expect(response.body).toBeDefined();
|
|
69
|
+
expect(response.body).toHaveLength(1);
|
|
70
|
+
expect(response.body[0].group.id).toBe(group.id);
|
|
71
|
+
expect(response.body[0].group.name.toString()).toBe(groupName);
|
|
72
|
+
expect(response.body[0].member.id).toBe(member.id);
|
|
73
|
+
expect(response.body[0].member.firstName).toBe(member.firstName);
|
|
74
|
+
expect(response.body[0].member.lastName).toBe(member.lastName);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Should fail if no write access to group', async () => {
|
|
78
|
+
const organization = await new OrganizationFactory({}).create();
|
|
79
|
+
const groupName = 'test groep';
|
|
80
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
81
|
+
const resources = new Map();
|
|
82
|
+
|
|
83
|
+
resources.set(
|
|
84
|
+
PermissionsResourceType.Groups, new Map([[
|
|
85
|
+
group.id,
|
|
86
|
+
ResourcePermissions.create({
|
|
87
|
+
// read access!
|
|
88
|
+
level: PermissionLevel.Read,
|
|
89
|
+
}),
|
|
90
|
+
]]),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const user = await new UserFactory({
|
|
94
|
+
organization,
|
|
95
|
+
permissions: Permissions.create({
|
|
96
|
+
level: PermissionLevel.None,
|
|
97
|
+
resources,
|
|
98
|
+
}),
|
|
99
|
+
}).create();
|
|
100
|
+
|
|
101
|
+
const member = await new MemberFactory({
|
|
102
|
+
organization, user,
|
|
103
|
+
firstName: 'John',
|
|
104
|
+
lastName: 'Doe',
|
|
105
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
106
|
+
}).create();
|
|
107
|
+
|
|
108
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
109
|
+
|
|
110
|
+
patch.addPut(RegistrationInvitationRequest.create({
|
|
111
|
+
groupId: group.id,
|
|
112
|
+
memberId: member.id,
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
// assert
|
|
116
|
+
await expect(patchInvitations({
|
|
117
|
+
patch,
|
|
118
|
+
organization,
|
|
119
|
+
user,
|
|
120
|
+
})).rejects.toThrow('Je hebt geen toegansrechten om iemand uit te nodigen voor deze groep.');
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('Should fail if no read access to member', async () => {
|
|
124
|
+
const organization = await new OrganizationFactory({}).create();
|
|
125
|
+
const groupName = 'test groep';
|
|
126
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
127
|
+
const resources = new Map();
|
|
128
|
+
|
|
129
|
+
resources.set(
|
|
130
|
+
PermissionsResourceType.Groups, new Map([[
|
|
131
|
+
group.id,
|
|
132
|
+
ResourcePermissions.create({
|
|
133
|
+
level: PermissionLevel.Write,
|
|
134
|
+
}),
|
|
135
|
+
]]),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const user = await new UserFactory({
|
|
139
|
+
organization,
|
|
140
|
+
permissions: Permissions.create({
|
|
141
|
+
level: PermissionLevel.None,
|
|
142
|
+
resources,
|
|
143
|
+
}),
|
|
144
|
+
}).create();
|
|
145
|
+
|
|
146
|
+
const member = await new MemberFactory({
|
|
147
|
+
organization,
|
|
148
|
+
firstName: 'John',
|
|
149
|
+
lastName: 'Doe',
|
|
150
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
151
|
+
}).create();
|
|
152
|
+
|
|
153
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
154
|
+
|
|
155
|
+
patch.addPut(RegistrationInvitationRequest.create({
|
|
156
|
+
groupId: group.id,
|
|
157
|
+
memberId: member.id,
|
|
158
|
+
}))
|
|
159
|
+
|
|
160
|
+
// assert
|
|
161
|
+
await expect(patchInvitations({
|
|
162
|
+
patch,
|
|
163
|
+
organization,
|
|
164
|
+
user,
|
|
165
|
+
})).rejects.toThrow('Je hebt geen toegansrechten om dit lid uit te nodigen.');
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('Should fail if already registered', async () => {
|
|
169
|
+
const organization = await new OrganizationFactory({}).create();
|
|
170
|
+
const groupName = 'test groep';
|
|
171
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
172
|
+
const resources = new Map();
|
|
173
|
+
|
|
174
|
+
resources.set(
|
|
175
|
+
PermissionsResourceType.Groups, new Map([[
|
|
176
|
+
group.id,
|
|
177
|
+
ResourcePermissions.create({
|
|
178
|
+
level: PermissionLevel.Write,
|
|
179
|
+
}),
|
|
180
|
+
]]),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const user = await new UserFactory({
|
|
184
|
+
organization,
|
|
185
|
+
permissions: Permissions.create({
|
|
186
|
+
level: PermissionLevel.None,
|
|
187
|
+
resources,
|
|
188
|
+
}),
|
|
189
|
+
}).create();
|
|
190
|
+
|
|
191
|
+
const member = await new MemberFactory({
|
|
192
|
+
organization, user,
|
|
193
|
+
firstName: 'John',
|
|
194
|
+
lastName: 'Doe',
|
|
195
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
196
|
+
}).create();
|
|
197
|
+
|
|
198
|
+
await new RegistrationFactory({member, group}).create();
|
|
199
|
+
|
|
200
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
201
|
+
|
|
202
|
+
patch.addPut(RegistrationInvitationRequest.create({
|
|
203
|
+
groupId: group.id,
|
|
204
|
+
memberId: member.id,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
// assert
|
|
208
|
+
await expect(patchInvitations({
|
|
209
|
+
patch,
|
|
210
|
+
organization,
|
|
211
|
+
user,
|
|
212
|
+
})).rejects.toThrow('The member is already registered for this group');
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('Patch invitation', () => {
|
|
218
|
+
test('Should not be supported', async () => {
|
|
219
|
+
const organization = await new OrganizationFactory({}).create();
|
|
220
|
+
const groupName = 'test groep';
|
|
221
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
222
|
+
const resources = new Map();
|
|
223
|
+
|
|
224
|
+
resources.set(
|
|
225
|
+
PermissionsResourceType.Groups, new Map([[
|
|
226
|
+
group.id,
|
|
227
|
+
ResourcePermissions.create({
|
|
228
|
+
level: PermissionLevel.Write,
|
|
229
|
+
}),
|
|
230
|
+
]]),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const user = await new UserFactory({
|
|
234
|
+
organization,
|
|
235
|
+
permissions: Permissions.create({
|
|
236
|
+
level: PermissionLevel.None,
|
|
237
|
+
resources,
|
|
238
|
+
}),
|
|
239
|
+
}).create();
|
|
240
|
+
|
|
241
|
+
const member = await new MemberFactory({
|
|
242
|
+
organization, user,
|
|
243
|
+
firstName: 'John',
|
|
244
|
+
lastName: 'Doe',
|
|
245
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
246
|
+
}).create();
|
|
247
|
+
|
|
248
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
249
|
+
|
|
250
|
+
patch.addPatch(RegistrationInvitationRequest.patch({
|
|
251
|
+
id: 'does not matter',
|
|
252
|
+
groupId: group.id,
|
|
253
|
+
memberId: member.id,
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
// assert
|
|
257
|
+
await expect(patchInvitations({
|
|
258
|
+
patch,
|
|
259
|
+
organization,
|
|
260
|
+
user,
|
|
261
|
+
})).rejects.toThrow('Patching invitations is not supported. Only puts and deletes are supported.');
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('Delete invitation', () => {
|
|
266
|
+
test('Happy path', async () => {
|
|
267
|
+
const organization = await new OrganizationFactory({}).create();
|
|
268
|
+
const groupName = 'test groep';
|
|
269
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
270
|
+
const resources = new Map();
|
|
271
|
+
|
|
272
|
+
resources.set(
|
|
273
|
+
PermissionsResourceType.Groups, new Map([[
|
|
274
|
+
group.id,
|
|
275
|
+
ResourcePermissions.create({
|
|
276
|
+
level: PermissionLevel.Write,
|
|
277
|
+
}),
|
|
278
|
+
]]),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const user = await new UserFactory({
|
|
282
|
+
organization,
|
|
283
|
+
permissions: Permissions.create({
|
|
284
|
+
level: PermissionLevel.None,
|
|
285
|
+
resources,
|
|
286
|
+
}),
|
|
287
|
+
}).create();
|
|
288
|
+
|
|
289
|
+
const member = await new MemberFactory({
|
|
290
|
+
organization,
|
|
291
|
+
user,
|
|
292
|
+
firstName: 'John',
|
|
293
|
+
lastName: 'Doe',
|
|
294
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
295
|
+
}).create();
|
|
296
|
+
|
|
297
|
+
const invitation = await new RegistrationInvitationFactory({member, group, organization}).create();
|
|
298
|
+
|
|
299
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
300
|
+
|
|
301
|
+
patch.addDelete(invitation.id);
|
|
302
|
+
|
|
303
|
+
const response = await patchInvitations({
|
|
304
|
+
patch,
|
|
305
|
+
organization,
|
|
306
|
+
user,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// assert
|
|
310
|
+
expect(response.body).toBeDefined();
|
|
311
|
+
expect(response.body).toHaveLength(0);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('Should be able to delete if access to group but not to member', async () => {
|
|
315
|
+
const organization = await new OrganizationFactory({}).create();
|
|
316
|
+
const groupName = 'test groep';
|
|
317
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
318
|
+
const resources = new Map();
|
|
319
|
+
|
|
320
|
+
resources.set(
|
|
321
|
+
PermissionsResourceType.Groups, new Map([[
|
|
322
|
+
group.id,
|
|
323
|
+
ResourcePermissions.create({
|
|
324
|
+
level: PermissionLevel.Write,
|
|
325
|
+
}),
|
|
326
|
+
]]),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const user = await new UserFactory({
|
|
330
|
+
organization,
|
|
331
|
+
permissions: Permissions.create({
|
|
332
|
+
level: PermissionLevel.None,
|
|
333
|
+
resources,
|
|
334
|
+
}),
|
|
335
|
+
}).create();
|
|
336
|
+
|
|
337
|
+
// user has no access to this member
|
|
338
|
+
const member = await new MemberFactory({
|
|
339
|
+
organization,
|
|
340
|
+
firstName: 'John',
|
|
341
|
+
lastName: 'Doe',
|
|
342
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
343
|
+
}).create();
|
|
344
|
+
|
|
345
|
+
const invitation = await new RegistrationInvitationFactory({member, group, organization}).create();
|
|
346
|
+
|
|
347
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
348
|
+
|
|
349
|
+
patch.addDelete(invitation.id);
|
|
350
|
+
|
|
351
|
+
const response = await patchInvitations({
|
|
352
|
+
patch,
|
|
353
|
+
organization,
|
|
354
|
+
user,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// assert
|
|
358
|
+
expect(response.body).toBeDefined();
|
|
359
|
+
expect(response.body).toHaveLength(0);
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
test('Should fail if no write access to group', async () => {
|
|
363
|
+
const organization = await new OrganizationFactory({}).create();
|
|
364
|
+
const groupName = 'test groep';
|
|
365
|
+
const group = await new GroupFactory({ organization, name: new TranslatedString(groupName) }).create();
|
|
366
|
+
const resources = new Map();
|
|
367
|
+
|
|
368
|
+
resources.set(
|
|
369
|
+
PermissionsResourceType.Groups, new Map([[
|
|
370
|
+
group.id,
|
|
371
|
+
ResourcePermissions.create({
|
|
372
|
+
level: PermissionLevel.Read,
|
|
373
|
+
}),
|
|
374
|
+
]]),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const user = await new UserFactory({
|
|
378
|
+
organization,
|
|
379
|
+
permissions: Permissions.create({
|
|
380
|
+
level: PermissionLevel.None,
|
|
381
|
+
resources,
|
|
382
|
+
}),
|
|
383
|
+
}).create();
|
|
384
|
+
|
|
385
|
+
const member = await new MemberFactory({
|
|
386
|
+
organization, user,
|
|
387
|
+
firstName: 'John',
|
|
388
|
+
lastName: 'Doe',
|
|
389
|
+
birthDay: {year: 1994, month: 6, day: 24}
|
|
390
|
+
}).create();
|
|
391
|
+
|
|
392
|
+
const invitation = await new RegistrationInvitationFactory({member, group, organization}).create();
|
|
393
|
+
|
|
394
|
+
const patch: PatchableArrayAutoEncoder<RegistrationInvitationRequest> = new PatchableArray();
|
|
395
|
+
|
|
396
|
+
patch.addDelete(invitation.id);
|
|
397
|
+
|
|
398
|
+
await expect(patchInvitations({
|
|
399
|
+
patch,
|
|
400
|
+
organization,
|
|
401
|
+
user,
|
|
402
|
+
})).rejects.toThrow('Je hebt geen toegansrechten om deze uitnodiging te verwijderen.');
|
|
403
|
+
})
|
|
404
|
+
});
|
|
405
|
+
});
|