@stamhoofd/backend 2.8.0 → 2.13.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/.env.template.json +3 -1
- package/package.json +11 -3
- package/src/crons.ts +3 -3
- package/src/decoders/StringArrayDecoder.ts +24 -0
- package/src/decoders/StringNullableDecoder.ts +18 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +20 -18
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +1 -0
- package/src/endpoints/global/events/GetEventsEndpoint.ts +3 -9
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +21 -1
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +79 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +15 -62
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +2 -2
- package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +3 -3
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +165 -35
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +20 -23
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +22 -1
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +56 -3
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +3 -3
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +3 -3
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +292 -170
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +22 -37
- package/src/endpoints/organization/dashboard/payments/legacy/GetPaymentsEndpoint.ts +170 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +1 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +14 -4
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +12 -2
- package/src/helpers/AdminPermissionChecker.ts +95 -60
- package/src/helpers/AuthenticatedStructures.ts +16 -6
- package/src/helpers/Context.ts +21 -0
- package/src/helpers/EmailResumer.ts +22 -2
- package/src/helpers/MemberUserSyncer.ts +8 -2
- package/src/helpers/ViesHelper.ts +151 -0
- package/src/seeds/1722344160-update-membership.ts +19 -22
- package/src/seeds/1722344161-sync-member-users.ts +60 -0
- package/.env.json +0 -65
package/.env.template.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"rendererApi": "renderer.stamhoofd"
|
|
23
23
|
},
|
|
24
24
|
"translationNamespace": "stamhoofd",
|
|
25
|
+
"platformName": "stamhoofd",
|
|
25
26
|
"userMode": "organization",
|
|
26
27
|
|
|
27
28
|
"PORT": 9091,
|
|
@@ -59,5 +60,6 @@
|
|
|
59
60
|
|
|
60
61
|
"NOLT_SSO_SECRET_KEY": "",
|
|
61
62
|
"INTERNAL_SECRET_KEY": "",
|
|
62
|
-
"CRONS_DISABLED": false
|
|
63
|
+
"CRONS_DISABLED": false,
|
|
64
|
+
"WHITELISTED_EMAIL_DESTINATIONS": ["*"]
|
|
63
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamhoofd/backend",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"license": "UNLICENCED",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"dev": "
|
|
12
|
+
"dev": "echo 'Waiting for shared backend packages...' && wait-on ../../shared/env/dist/index.js && echo 'Start building backend API' && concurrently -r 'yarn build --watch --preserveWatchOutput' \"wait-on ./dist/index.js && nodemon --quiet --inspect=5858 --watch dist --watch '../../../shared/*/dist/' --watch '../../shared/*/dist/' --ext .ts,.json,.sql,.js --watch .env.json --delay 1000ms --exec 'node --enable-source-maps ./dist/index.js' --signal SIGTERM\"",
|
|
13
13
|
"dev:backend": "yarn dev",
|
|
14
14
|
"build": "rm -rf ./dist/src/migrations && rm -rf ./dist/src/seeds && tsc -b",
|
|
15
15
|
"build:full": "yarn clear && yarn build",
|
|
@@ -35,6 +35,14 @@
|
|
|
35
35
|
"@simonbackx/simple-database": "1.24.0",
|
|
36
36
|
"@simonbackx/simple-endpoints": "1.13.0",
|
|
37
37
|
"@simonbackx/simple-logging": "^1.0.1",
|
|
38
|
+
"@stamhoofd/backend-i18n": "^2.13.0",
|
|
39
|
+
"@stamhoofd/backend-middleware": "^2.13.0",
|
|
40
|
+
"@stamhoofd/email": "^2.13.0",
|
|
41
|
+
"@stamhoofd/models": "^2.13.0",
|
|
42
|
+
"@stamhoofd/queues": "^2.13.0",
|
|
43
|
+
"@stamhoofd/sql": "^2.13.0",
|
|
44
|
+
"@stamhoofd/structures": "^2.13.0",
|
|
45
|
+
"@stamhoofd/utility": "^2.13.0",
|
|
38
46
|
"aws-sdk": "^2.885.0",
|
|
39
47
|
"axios": "1.6.8",
|
|
40
48
|
"cookie": "^0.5.0",
|
|
@@ -50,5 +58,5 @@
|
|
|
50
58
|
"postmark": "4.0.2",
|
|
51
59
|
"stripe": "^16.6.0"
|
|
52
60
|
},
|
|
53
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "2b130616eebea8cd30feb0f361c7bb0f48b534ba"
|
|
54
62
|
}
|
package/src/crons.ts
CHANGED
|
@@ -140,7 +140,7 @@ async function checkWebshopDNS() {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
async function checkReplies() {
|
|
143
|
-
if (STAMHOOFD.environment
|
|
143
|
+
if (STAMHOOFD.environment !== "production") {
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -200,7 +200,7 @@ async function checkReplies() {
|
|
|
200
200
|
let lastPostmarkCheck: Date | null = null
|
|
201
201
|
let lastPostmarkId: string | null = null
|
|
202
202
|
async function checkPostmarkBounces() {
|
|
203
|
-
if (STAMHOOFD.environment
|
|
203
|
+
if (STAMHOOFD.environment !== "production") {
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
206
|
|
|
@@ -842,4 +842,4 @@ export const crons = async () => {
|
|
|
842
842
|
}
|
|
843
843
|
}
|
|
844
844
|
schedulingJobs = false;
|
|
845
|
-
};
|
|
845
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Data, Decoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
|
|
3
|
+
export class StringArrayDecoder<T> implements Decoder<T[]> {
|
|
4
|
+
decoder: Decoder<T>;
|
|
5
|
+
|
|
6
|
+
constructor(decoder: Decoder<T>) {
|
|
7
|
+
this.decoder = decoder;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
decode(data: Data): T[] {
|
|
11
|
+
const strValue = data.string;
|
|
12
|
+
|
|
13
|
+
// Split on comma
|
|
14
|
+
const parts = strValue.split(",");
|
|
15
|
+
return parts
|
|
16
|
+
.map((v, index) => {
|
|
17
|
+
return data.clone({
|
|
18
|
+
data: v,
|
|
19
|
+
context: data.context,
|
|
20
|
+
field: data.addToCurrentField(index)
|
|
21
|
+
}).decode(this.decoder)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Decoder, Data } from "@simonbackx/simple-encoding";
|
|
2
|
+
|
|
3
|
+
export class StringNullableDecoder<T> implements Decoder<T | null> {
|
|
4
|
+
decoder: Decoder<T>;
|
|
5
|
+
|
|
6
|
+
constructor(decoder: Decoder<T>) {
|
|
7
|
+
this.decoder = decoder;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
decode(data: Data): T | null {
|
|
11
|
+
if (data.value === 'null') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return data.decode(this.decoder);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -19,6 +19,9 @@ export const filterCompilers: SQLFilterDefinitions = {
|
|
|
19
19
|
id: createSQLExpressionFilterCompiler(
|
|
20
20
|
SQL.column('organizations', 'id')
|
|
21
21
|
),
|
|
22
|
+
uri: createSQLExpressionFilterCompiler(
|
|
23
|
+
SQL.column('organizations', 'uri')
|
|
24
|
+
),
|
|
22
25
|
name: createSQLExpressionFilterCompiler(
|
|
23
26
|
SQL.column('organizations', 'name')
|
|
24
27
|
),
|
|
@@ -27,33 +30,23 @@ export const filterCompilers: SQLFilterDefinitions = {
|
|
|
27
30
|
),
|
|
28
31
|
city: createSQLExpressionFilterCompiler(
|
|
29
32
|
SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.city'),
|
|
30
|
-
|
|
31
|
-
true,
|
|
32
|
-
false
|
|
33
|
+
{isJSONValue: true}
|
|
33
34
|
),
|
|
34
35
|
country: createSQLExpressionFilterCompiler(
|
|
35
36
|
SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.country'),
|
|
36
|
-
|
|
37
|
-
true,
|
|
38
|
-
false
|
|
37
|
+
{isJSONValue: true}
|
|
39
38
|
),
|
|
40
39
|
umbrellaOrganization: createSQLExpressionFilterCompiler(
|
|
41
40
|
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.umbrellaOrganization'),
|
|
42
|
-
|
|
43
|
-
true,
|
|
44
|
-
false
|
|
41
|
+
{isJSONValue: true}
|
|
45
42
|
),
|
|
46
43
|
type: createSQLExpressionFilterCompiler(
|
|
47
44
|
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.type'),
|
|
48
|
-
|
|
49
|
-
true,
|
|
50
|
-
false
|
|
45
|
+
{isJSONValue: true}
|
|
51
46
|
),
|
|
52
47
|
tags: createSQLExpressionFilterCompiler(
|
|
53
48
|
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.tags'),
|
|
54
|
-
|
|
55
|
-
true,
|
|
56
|
-
true
|
|
49
|
+
{isJSONValue: true, isJSONObject: true}
|
|
57
50
|
),
|
|
58
51
|
packages: createSQLRelationFilterCompiler(
|
|
59
52
|
SQL.select().from(
|
|
@@ -100,9 +93,7 @@ export const filterCompilers: SQLFilterDefinitions = {
|
|
|
100
93
|
...baseSQLFilterCompilers,
|
|
101
94
|
"type": createSQLExpressionFilterCompiler(
|
|
102
95
|
SQL.jsonValue(SQL.column('meta'), '$.value.type'),
|
|
103
|
-
|
|
104
|
-
true,
|
|
105
|
-
false
|
|
96
|
+
{isJSONValue: true}
|
|
106
97
|
)
|
|
107
98
|
}
|
|
108
99
|
),
|
|
@@ -160,6 +151,17 @@ const sorters: SQLSortDefinitions<Organization> = {
|
|
|
160
151
|
})
|
|
161
152
|
}
|
|
162
153
|
},
|
|
154
|
+
'uri': {
|
|
155
|
+
getValue(a) {
|
|
156
|
+
return a.uri
|
|
157
|
+
},
|
|
158
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
159
|
+
return new SQLOrderBy({
|
|
160
|
+
column: SQL.column('uri'),
|
|
161
|
+
direction
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
},
|
|
163
165
|
'type': {
|
|
164
166
|
getValue(a) {
|
|
165
167
|
return a.meta.type
|
|
@@ -100,6 +100,7 @@ export class PatchEmailEndpoint extends Endpoint<Params, Query, Body, ResponseBo
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
if (request.body.status === EmailStatus.Sending || request.body.status === EmailStatus.Sent) {
|
|
103
|
+
model.throwIfNotReadyToSend()
|
|
103
104
|
model.send().catch(console.error)
|
|
104
105
|
}
|
|
105
106
|
|
|
@@ -24,21 +24,15 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
24
24
|
endDate: createSQLColumnFilterCompiler('endDate'),
|
|
25
25
|
groupIds: createSQLExpressionFilterCompiler(
|
|
26
26
|
SQL.jsonValue(SQL.column('meta'), '$.value.groups[*].id'),
|
|
27
|
-
|
|
28
|
-
true,
|
|
29
|
-
true
|
|
27
|
+
{isJSONValue: true, isJSONObject: true}
|
|
30
28
|
),
|
|
31
29
|
defaultAgeGroupIds: createSQLExpressionFilterCompiler(
|
|
32
30
|
SQL.jsonValue(SQL.column('meta'), '$.value.defaultAgeGroupIds'),
|
|
33
|
-
|
|
34
|
-
true,
|
|
35
|
-
true
|
|
31
|
+
{isJSONValue: true, isJSONObject: true}
|
|
36
32
|
),
|
|
37
33
|
organizationTagIds: createSQLExpressionFilterCompiler(
|
|
38
34
|
SQL.jsonValue(SQL.column('meta'), '$.value.organizationTagIds'),
|
|
39
|
-
|
|
40
|
-
true,
|
|
41
|
-
true
|
|
35
|
+
{isJSONValue: true, isJSONObject: true}
|
|
42
36
|
)
|
|
43
37
|
}
|
|
44
38
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutoEncoderPatchType, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, patchObject, StringDecoder } from '@simonbackx/simple-encoding';
|
|
2
2
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
3
|
import { Event, Group, Organization, Platform, RegistrationPeriod } from '@stamhoofd/models';
|
|
4
|
-
import { Event as EventStruct, GroupType, PermissionLevel } from "@stamhoofd/structures";
|
|
4
|
+
import { Event as EventStruct, GroupType, NamedObject, PermissionLevel } from "@stamhoofd/structures";
|
|
5
5
|
|
|
6
6
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
7
7
|
import { SQL, SQLWhereSign } from '@stamhoofd/sql';
|
|
@@ -81,6 +81,7 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
81
81
|
event.endDate = put.endDate
|
|
82
82
|
event.meta = put.meta
|
|
83
83
|
event.typeId = await PatchEventsEndpoint.validateEventType(put.typeId)
|
|
84
|
+
event.meta.organizationCache = eventOrganization ? NamedObject.create({id: eventOrganization.id, name: eventOrganization.name}) : null
|
|
84
85
|
await PatchEventsEndpoint.checkEventLimits(event)
|
|
85
86
|
|
|
86
87
|
if (!(await Context.auth.canAccessEvent(event, PermissionLevel.Full))) {
|
|
@@ -128,6 +129,16 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
128
129
|
event.name = patch.name ?? event.name
|
|
129
130
|
event.startDate = patch.startDate ?? event.startDate
|
|
130
131
|
event.endDate = patch.endDate ?? event.endDate
|
|
132
|
+
|
|
133
|
+
if (patch.meta?.organizationCache) {
|
|
134
|
+
throw new SimpleError({
|
|
135
|
+
code: 'invalid_field',
|
|
136
|
+
message: 'Cannot patch organizationCache',
|
|
137
|
+
human: 'Je kan de organizationCache niet aanpassen via een patch',
|
|
138
|
+
field: 'meta.organizationCache'
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
event.meta = patchObject(event.meta, patch.meta)
|
|
132
143
|
|
|
133
144
|
if (patch.organizationId !== undefined) {
|
|
@@ -156,6 +167,15 @@ export class PatchEventsEndpoint extends Endpoint<Params, Query, Body, ResponseB
|
|
|
156
167
|
})
|
|
157
168
|
}
|
|
158
169
|
event.organizationId = patch.organizationId
|
|
170
|
+
event.meta.organizationCache = eventOrganization ? NamedObject.create({id: eventOrganization.id, name: eventOrganization.name}) : null
|
|
171
|
+
} else {
|
|
172
|
+
// Update cache
|
|
173
|
+
if (event.organizationId) {
|
|
174
|
+
const eventOrganization = await Organization.getByID(event.organizationId)
|
|
175
|
+
if (eventOrganization) {
|
|
176
|
+
event.meta.organizationCache = NamedObject.create({id: eventOrganization.id, name: eventOrganization.name})
|
|
177
|
+
}
|
|
178
|
+
}
|
|
159
179
|
}
|
|
160
180
|
|
|
161
181
|
event.typeId = patch.typeId ? (await PatchEventsEndpoint.validateEventType(patch.typeId)) : event.typeId
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { Group, Organization } from '@stamhoofd/models';
|
|
3
|
+
import { GroupsWithOrganizations } from "@stamhoofd/structures";
|
|
4
|
+
|
|
5
|
+
import { AutoEncoder, Decoder, field, StringDecoder } from "@simonbackx/simple-encoding";
|
|
6
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
7
|
+
import { StringArrayDecoder } from "../../../decoders/StringArrayDecoder";
|
|
8
|
+
import { AuthenticatedStructures } from "../../../helpers/AuthenticatedStructures";
|
|
9
|
+
import { Context } from "../../../helpers/Context";
|
|
10
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
11
|
+
type Params = Record<string, never>;
|
|
12
|
+
|
|
13
|
+
class Query extends AutoEncoder {
|
|
14
|
+
@field({ decoder: new StringArrayDecoder(StringDecoder) })
|
|
15
|
+
ids: string[]
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List of organizations the requester already knows and doesn't need to be included in the response
|
|
19
|
+
*/
|
|
20
|
+
@field({ decoder: new StringArrayDecoder(StringDecoder), optional: true })
|
|
21
|
+
excludeOrganizationIds: string[] = []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Body = undefined
|
|
25
|
+
type ResponseBody = GroupsWithOrganizations
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the members of the user
|
|
29
|
+
*/
|
|
30
|
+
export class GetGroupsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
31
|
+
queryDecoder = Query as Decoder<Query>
|
|
32
|
+
|
|
33
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
34
|
+
if (request.method != "GET") {
|
|
35
|
+
return [false];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const params = Endpoint.parseParameters(request.url, "/groups", {});
|
|
39
|
+
|
|
40
|
+
if (params) {
|
|
41
|
+
return [true, params as Params];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return [false];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
48
|
+
await Context.setOptionalOrganizationScope();
|
|
49
|
+
await Context.optionalAuthenticate()
|
|
50
|
+
|
|
51
|
+
if (request.query.ids.length === 0) {
|
|
52
|
+
return new Response(
|
|
53
|
+
GroupsWithOrganizations.create({
|
|
54
|
+
groups: [],
|
|
55
|
+
organizations: []
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (request.query.ids.length > 100) {
|
|
61
|
+
throw new SimpleError({
|
|
62
|
+
code: "too_many_ids",
|
|
63
|
+
message: "You can't request more than 100 groups at once"
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const groups = await Group.getByIDs(...request.query.ids)
|
|
68
|
+
const organizationIds = Formatter.uniqueArray(groups.map(g => g.organizationId).filter(id => !request.query.excludeOrganizationIds.includes(id)));
|
|
69
|
+
|
|
70
|
+
const organizations = organizationIds.length > 0 ? (await Organization.getByIDs(...organizationIds)) : [];
|
|
71
|
+
|
|
72
|
+
return new Response(
|
|
73
|
+
GroupsWithOrganizations.create({
|
|
74
|
+
groups: await AuthenticatedStructures.groups(groups),
|
|
75
|
+
organizations: await AuthenticatedStructures.organizations(organizations)
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -75,25 +75,12 @@ Email.recipientLoaders.set(EmailRecipientFilterType.MemberUnverified, {
|
|
|
75
75
|
|
|
76
76
|
const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
77
77
|
...baseSQLFilterCompilers,
|
|
78
|
-
"price": createSQLColumnFilterCompiler('price'),
|
|
78
|
+
"price": createSQLColumnFilterCompiler('price', {nullable: true}),
|
|
79
79
|
"pricePaid": createSQLColumnFilterCompiler('pricePaid'),
|
|
80
|
-
"waitingList": createSQLColumnFilterCompiler('waitingList'),
|
|
81
80
|
"canRegister": createSQLColumnFilterCompiler('canRegister'),
|
|
82
|
-
"cycle": createSQLColumnFilterCompiler('cycle'),
|
|
83
|
-
|
|
84
|
-
"cycleOffset": createSQLExpressionFilterCompiler({
|
|
85
|
-
getSQL(options) {
|
|
86
|
-
return joinSQLQuery([
|
|
87
|
-
SQL.column('groups', 'cycle').getSQL(options),
|
|
88
|
-
' - ',
|
|
89
|
-
SQL.column('registrations', 'cycle').getSQL(options)
|
|
90
|
-
])
|
|
91
|
-
},
|
|
92
|
-
}),
|
|
93
|
-
|
|
94
81
|
"organizationId": createSQLColumnFilterCompiler('organizationId'),
|
|
95
82
|
"groupId": createSQLColumnFilterCompiler('groupId'),
|
|
96
|
-
"registeredAt": createSQLColumnFilterCompiler('registeredAt'),
|
|
83
|
+
"registeredAt": createSQLColumnFilterCompiler('registeredAt', {nullable: true}),
|
|
97
84
|
"periodId": createSQLColumnFilterCompiler(SQL.column('registrations', 'periodId')),
|
|
98
85
|
|
|
99
86
|
"group": createSQLFilterNamespace({
|
|
@@ -105,7 +92,7 @@ const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
|
105
92
|
status: createSQLExpressionFilterCompiler(
|
|
106
93
|
SQL.column('groups', 'status')
|
|
107
94
|
),
|
|
108
|
-
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
|
|
95
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), {nullable: true}),
|
|
109
96
|
})
|
|
110
97
|
}
|
|
111
98
|
|
|
@@ -120,20 +107,21 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
120
107
|
)
|
|
121
108
|
),
|
|
122
109
|
age: createSQLExpressionFilterCompiler(
|
|
123
|
-
new SQLAge(SQL.column('birthDay'))
|
|
110
|
+
new SQLAge(SQL.column('birthDay')),
|
|
111
|
+
{nullable: true}
|
|
124
112
|
),
|
|
125
113
|
gender: createSQLExpressionFilterCompiler(
|
|
126
114
|
SQL.jsonValue(SQL.column('details'), '$.value.gender'),
|
|
127
|
-
|
|
128
|
-
true,
|
|
129
|
-
false
|
|
115
|
+
{isJSONValue: true}
|
|
130
116
|
),
|
|
131
|
-
birthDay: createSQLColumnFilterCompiler('birthDay',
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
birthDay: createSQLColumnFilterCompiler('birthDay', {
|
|
118
|
+
normalizeValue: (d) => {
|
|
119
|
+
if (typeof d === 'number') {
|
|
120
|
+
const date = new Date(d)
|
|
121
|
+
return Formatter.dateIso(date);
|
|
122
|
+
}
|
|
123
|
+
return d;
|
|
135
124
|
}
|
|
136
|
-
return d;
|
|
137
125
|
}),
|
|
138
126
|
organizationName: createSQLExpressionFilterCompiler(
|
|
139
127
|
SQL.column('organizations', 'name')
|
|
@@ -141,16 +129,12 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
141
129
|
|
|
142
130
|
email: createSQLExpressionFilterCompiler(
|
|
143
131
|
SQL.jsonValue(SQL.column('details'), '$.value.email'),
|
|
144
|
-
|
|
145
|
-
true,
|
|
146
|
-
false
|
|
132
|
+
{isJSONValue: true}
|
|
147
133
|
),
|
|
148
134
|
|
|
149
135
|
parentEmail: createSQLExpressionFilterCompiler(
|
|
150
136
|
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].email'),
|
|
151
|
-
|
|
152
|
-
true,
|
|
153
|
-
true
|
|
137
|
+
{isJSONValue: true, isJSONObject: true}
|
|
154
138
|
),
|
|
155
139
|
|
|
156
140
|
registrations: createSQLRelationFilterCompiler(
|
|
@@ -252,37 +236,6 @@ const filterCompilers: SQLFilterDefinitions = {
|
|
|
252
236
|
}
|
|
253
237
|
),
|
|
254
238
|
|
|
255
|
-
/**
|
|
256
|
-
* @deprecated?
|
|
257
|
-
*/
|
|
258
|
-
activeRegistrations: createSQLRelationFilterCompiler(
|
|
259
|
-
SQL.select()
|
|
260
|
-
.from(
|
|
261
|
-
SQL.table('registrations')
|
|
262
|
-
).join(
|
|
263
|
-
SQL.join(
|
|
264
|
-
SQL.table('groups')
|
|
265
|
-
).where(
|
|
266
|
-
SQL.column('groups', 'id'),
|
|
267
|
-
SQL.column('registrations', 'groupId')
|
|
268
|
-
)
|
|
269
|
-
)
|
|
270
|
-
.where(
|
|
271
|
-
SQL.column('memberId'),
|
|
272
|
-
SQL.column('members', 'id'),
|
|
273
|
-
).whereNot(
|
|
274
|
-
SQL.column('registeredAt'),
|
|
275
|
-
null,
|
|
276
|
-
).where(
|
|
277
|
-
SQL.column('deactivatedAt'),
|
|
278
|
-
null,
|
|
279
|
-
).where(
|
|
280
|
-
SQL.column('groups', 'deletedAt'),
|
|
281
|
-
null
|
|
282
|
-
),
|
|
283
|
-
registrationFilterCompilers
|
|
284
|
-
),
|
|
285
|
-
|
|
286
239
|
organizations: createSQLRelationFilterCompiler(
|
|
287
240
|
SQL.select()
|
|
288
241
|
.from(
|
|
@@ -2,8 +2,8 @@ import { OneToManyRelation } from '@simonbackx/simple-database';
|
|
|
2
2
|
import { ConvertArrayToPatchableArray, Decoder, PatchableArrayAutoEncoder, PatchableArrayDecoder, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
3
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
4
4
|
import { SimpleError } from "@simonbackx/simple-errors";
|
|
5
|
-
import { BalanceItem,
|
|
6
|
-
import {
|
|
5
|
+
import { BalanceItem, Document, Group, Member, MemberFactory, MemberPlatformMembership, MemberResponsibilityRecord, MemberWithRegistrations, Organization, Platform, Registration, User } from '@stamhoofd/models';
|
|
6
|
+
import { MemberWithRegistrationsBlob, MembersBlob, PermissionLevel } from "@stamhoofd/structures";
|
|
7
7
|
import { Formatter } from '@stamhoofd/utility';
|
|
8
8
|
|
|
9
9
|
import { AuthenticatedStructures } from '../../../helpers/AuthenticatedStructures';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
2
|
import { BalanceItem, Member } from "@stamhoofd/models";
|
|
3
|
-
import {
|
|
3
|
+
import { BalanceItemWithPayments } from "@stamhoofd/structures";
|
|
4
4
|
|
|
5
5
|
import { Context } from "../../../helpers/Context";
|
|
6
6
|
|
|
7
7
|
type Params = Record<string, never>;
|
|
8
8
|
type Query = undefined
|
|
9
9
|
type Body = undefined
|
|
10
|
-
type ResponseBody =
|
|
10
|
+
type ResponseBody = BalanceItemWithPayments[]
|
|
11
11
|
|
|
12
12
|
export class GetUserBalanceEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
13
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
@@ -33,7 +33,7 @@ export class GetUserBalanceEndpoint extends Endpoint<Params, Query, Body, Respon
|
|
|
33
33
|
const balanceItems = await BalanceItem.balanceItemsForUsersAndMembers(organization?.id ?? null, [user.id], members.map(m => m.id))
|
|
34
34
|
|
|
35
35
|
return new Response(
|
|
36
|
-
await BalanceItem.
|
|
36
|
+
await BalanceItem.getStructureWithPayments(balanceItems)
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
}
|