@stamhoofd/backend 2.15.0 → 2.17.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 +2 -1
- package/index.ts +15 -1
- package/package.json +14 -12
- package/src/email-recipient-loaders/members.ts +61 -0
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +1 -1
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +5 -183
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +163 -0
- package/src/endpoints/global/files/GetFileCache.ts +69 -0
- package/src/endpoints/global/files/UploadFile.ts +4 -1
- package/src/endpoints/global/files/UploadImage.ts +14 -2
- package/src/endpoints/global/members/GetMembersEndpoint.ts +12 -299
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +22 -2
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +6 -134
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +5 -3
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +5 -3
- package/src/excel-loaders/members.ts +101 -0
- package/src/excel-loaders/payments.ts +539 -0
- package/src/helpers/AdminPermissionChecker.ts +0 -3
- package/src/helpers/AuthenticatedStructures.ts +2 -0
- package/src/helpers/FileCache.ts +158 -0
- package/src/helpers/fetchToAsyncIterator.ts +34 -0
- package/src/sql-filters/balance-item-payments.ts +13 -0
- package/src/sql-filters/members.ts +179 -0
- package/src/sql-filters/organizations.ts +115 -0
- package/src/sql-filters/payments.ts +78 -0
- package/src/sql-filters/registrations.ts +24 -0
- package/src/sql-sorters/members.ts +46 -0
- package/src/sql-sorters/organizations.ts +71 -0
- package/src/sql-sorters/payments.ts +50 -0
- package/tsconfig.json +3 -4
- package/src/endpoints/organization/dashboard/payments/legacy/GetPaymentsEndpoint.ts +0 -170
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
2
|
+
import { Formatter } from '@stamhoofd/utility';
|
|
3
|
+
import basex from "base-x";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { Writable } from 'node:stream';
|
|
7
|
+
import { Readable } from 'stream';
|
|
8
|
+
|
|
9
|
+
const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
|
|
10
|
+
const baseEncoder = basex(ALPHABET)
|
|
11
|
+
|
|
12
|
+
async function randomBytes(size: number): Promise<Buffer> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
crypto.randomBytes(size, (err: Error | null, buf: Buffer) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
reject(err);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
resolve(buf);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export class FileCache {
|
|
26
|
+
static async getWriteStream(extension: '.xlsx'): Promise<{
|
|
27
|
+
file: string,
|
|
28
|
+
stream: WritableStream
|
|
29
|
+
}> {
|
|
30
|
+
if (!STAMHOOFD.CACHE_PATH) {
|
|
31
|
+
throw new SimpleError({
|
|
32
|
+
code: "not_configured",
|
|
33
|
+
message: "CACHE_PATH environment variable is not configured",
|
|
34
|
+
statusCode: 500
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Generate a long and unguessable filename
|
|
39
|
+
const fileName = baseEncoder.encode(await randomBytes(100)).toLowerCase() + extension;
|
|
40
|
+
const path = Formatter.dateIso(new Date());
|
|
41
|
+
|
|
42
|
+
// Save in folder with the current day
|
|
43
|
+
// Since this contains the day, we can easily restrict access to files after 1 day
|
|
44
|
+
const folder = STAMHOOFD.CACHE_PATH + "/" + path;
|
|
45
|
+
await fs.promises.mkdir(folder, { recursive: true })
|
|
46
|
+
|
|
47
|
+
const s = fs.createWriteStream(folder + '/' + fileName, 'binary');
|
|
48
|
+
|
|
49
|
+
s.on('close', () => {
|
|
50
|
+
console.log('FileCache closed file: File written to disk', folder + '/' + fileName)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
file: path + '/' + fileName,
|
|
55
|
+
stream: Writable.toWeb(s)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async read(file: string, dayTimeout = 1): Promise<{stream: Readable, contentLength: number, extension: string}> {
|
|
60
|
+
const splitted = file.split("/");
|
|
61
|
+
if (splitted.length != 2) {
|
|
62
|
+
throw new SimpleError({
|
|
63
|
+
code: "invalid_file",
|
|
64
|
+
message: "Invalid file",
|
|
65
|
+
human: "Ongeldig bestand",
|
|
66
|
+
statusCode: 400
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fileName = splitted[1];
|
|
71
|
+
|
|
72
|
+
const extension = fileName.substring(fileName.length - 5);
|
|
73
|
+
if (extension != '.xlsx') {
|
|
74
|
+
throw new SimpleError({
|
|
75
|
+
code: "invalid_file",
|
|
76
|
+
message: "Invalid file",
|
|
77
|
+
human: "Ongeldig bestand",
|
|
78
|
+
statusCode: 400
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const fileNameWithoutExtension = fileName.substring(0, fileName.length - 5);
|
|
83
|
+
|
|
84
|
+
// Verify filename alphabet matching ALPHABET
|
|
85
|
+
for (const char of fileNameWithoutExtension) {
|
|
86
|
+
if (!ALPHABET.includes(char)) {
|
|
87
|
+
throw new SimpleError({
|
|
88
|
+
code: "invalid_file",
|
|
89
|
+
message: "Invalid file",
|
|
90
|
+
human: "Onbekend karakters in bestandsnaam",
|
|
91
|
+
statusCode: 400
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const path = splitted[0];
|
|
97
|
+
|
|
98
|
+
// Verify date
|
|
99
|
+
if (path.length != 10) {
|
|
100
|
+
throw new SimpleError({
|
|
101
|
+
code: "invalid_file",
|
|
102
|
+
message: "Invalid file",
|
|
103
|
+
human: "Ongelidge datum",
|
|
104
|
+
statusCode: 400
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
const year = parseInt(path.substring(0, 4));
|
|
108
|
+
const month = parseInt(path.substring(5, 5 + 2));
|
|
109
|
+
const day = parseInt(path.substring(8, 8 + 2));
|
|
110
|
+
|
|
111
|
+
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
112
|
+
throw new SimpleError({
|
|
113
|
+
code: "invalid_file",
|
|
114
|
+
message: "Invalid file",
|
|
115
|
+
human: "Ongeldige datum",
|
|
116
|
+
statusCode: 400
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const date = new Date(year, month - 1, day, 0, 0, 0);
|
|
121
|
+
const now = new Date();
|
|
122
|
+
now.setHours(0, 0, 0, 0);
|
|
123
|
+
|
|
124
|
+
const diff = now.getTime() - date.getTime()
|
|
125
|
+
|
|
126
|
+
if (date > now || diff > dayTimeout * 24 * 60 * 60 * 1000) {
|
|
127
|
+
throw new SimpleError({
|
|
128
|
+
code: "file_expired",
|
|
129
|
+
message: "File expired",
|
|
130
|
+
human: "Het bestand is verlopen",
|
|
131
|
+
statusCode: 404
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const filePath = STAMHOOFD.CACHE_PATH + "/" + Formatter.dateIso(date) + "/" + fileName;
|
|
136
|
+
|
|
137
|
+
let stat: fs.Stats;
|
|
138
|
+
try {
|
|
139
|
+
stat = await fs.promises.stat(filePath);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
if (e.code == 'ENOENT') {
|
|
142
|
+
throw new SimpleError({
|
|
143
|
+
code: "file_expired",
|
|
144
|
+
message: "File expired",
|
|
145
|
+
human: "Het bestand bestaat niet",
|
|
146
|
+
statusCode: 404
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
stream: fs.createReadStream(filePath),
|
|
154
|
+
contentLength: stat.size,
|
|
155
|
+
extension
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { EncodableObject } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { LimitedFilteredRequest, PaginatedResponse } from "@stamhoofd/structures";
|
|
3
|
+
|
|
4
|
+
export function fetchToAsyncIterator<T extends EncodableObject>(
|
|
5
|
+
initialFilter: LimitedFilteredRequest,
|
|
6
|
+
loader: {
|
|
7
|
+
fetch(request: LimitedFilteredRequest): Promise<PaginatedResponse<T, LimitedFilteredRequest>>
|
|
8
|
+
}
|
|
9
|
+
): AsyncIterable<T> {
|
|
10
|
+
return {
|
|
11
|
+
[Symbol.asyncIterator]: function () {
|
|
12
|
+
let request: LimitedFilteredRequest|null = initialFilter
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
async next(): Promise<IteratorResult<T, undefined>> {
|
|
16
|
+
if (!request) {
|
|
17
|
+
return {
|
|
18
|
+
done: true,
|
|
19
|
+
value: undefined
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const response = await loader.fetch(request);
|
|
24
|
+
request = response.next ?? null;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
done: false,
|
|
28
|
+
value: response.results
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler, SQL, createSQLFilterNamespace } from "@stamhoofd/sql";
|
|
2
|
+
|
|
3
|
+
export const balanceItemPaymentsCompilers: SQLFilterDefinitions = {
|
|
4
|
+
...baseSQLFilterCompilers,
|
|
5
|
+
"id": createSQLColumnFilterCompiler(SQL.column('balance_item_payments', 'id')),
|
|
6
|
+
"price": createSQLColumnFilterCompiler(SQL.column('balance_item_payments', 'price')),
|
|
7
|
+
|
|
8
|
+
"balanceItem": createSQLFilterNamespace({
|
|
9
|
+
...baseSQLFilterCompilers,
|
|
10
|
+
id: createSQLColumnFilterCompiler(SQL.column('balance_items', 'id')),
|
|
11
|
+
description: createSQLColumnFilterCompiler(SQL.column('balance_items', 'description')),
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { SQL, SQLAge, SQLConcat, SQLFilterDefinitions, SQLScalar, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
|
|
2
|
+
import { Formatter } from "@stamhoofd/utility";
|
|
3
|
+
import { organizationFilterCompilers } from "./organizations";
|
|
4
|
+
import { registrationFilterCompilers } from "./registrations";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Defines how to filter members in the database from StamhoofdFilter objects
|
|
8
|
+
*/
|
|
9
|
+
export const memberFilterCompilers: SQLFilterDefinitions = {
|
|
10
|
+
...baseSQLFilterCompilers,
|
|
11
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
12
|
+
name: createSQLExpressionFilterCompiler(
|
|
13
|
+
new SQLConcat(
|
|
14
|
+
SQL.column('firstName'),
|
|
15
|
+
new SQLScalar(' '),
|
|
16
|
+
SQL.column('lastName'),
|
|
17
|
+
)
|
|
18
|
+
),
|
|
19
|
+
age: createSQLExpressionFilterCompiler(
|
|
20
|
+
new SQLAge(SQL.column('birthDay')),
|
|
21
|
+
{nullable: true}
|
|
22
|
+
),
|
|
23
|
+
gender: createSQLExpressionFilterCompiler(
|
|
24
|
+
SQL.jsonValue(SQL.column('details'), '$.value.gender'),
|
|
25
|
+
{isJSONValue: true}
|
|
26
|
+
),
|
|
27
|
+
birthDay: createSQLColumnFilterCompiler('birthDay', {
|
|
28
|
+
normalizeValue: (d) => {
|
|
29
|
+
if (typeof d === 'number') {
|
|
30
|
+
const date = new Date(d)
|
|
31
|
+
return Formatter.dateIso(date);
|
|
32
|
+
}
|
|
33
|
+
return d;
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
organizationName: createSQLExpressionFilterCompiler(
|
|
37
|
+
SQL.column('organizations', 'name')
|
|
38
|
+
),
|
|
39
|
+
|
|
40
|
+
email: createSQLExpressionFilterCompiler(
|
|
41
|
+
SQL.jsonValue(SQL.column('details'), '$.value.email'),
|
|
42
|
+
{isJSONValue: true}
|
|
43
|
+
),
|
|
44
|
+
|
|
45
|
+
parentEmail: createSQLExpressionFilterCompiler(
|
|
46
|
+
SQL.jsonValue(SQL.column('details'), '$.value.parents[*].email'),
|
|
47
|
+
{isJSONValue: true, isJSONObject: true}
|
|
48
|
+
),
|
|
49
|
+
|
|
50
|
+
registrations: createSQLRelationFilterCompiler(
|
|
51
|
+
SQL.select()
|
|
52
|
+
.from(
|
|
53
|
+
SQL.table('registrations')
|
|
54
|
+
).join(
|
|
55
|
+
SQL.join(
|
|
56
|
+
SQL.table('groups')
|
|
57
|
+
).where(
|
|
58
|
+
SQL.column('groups', 'id'),
|
|
59
|
+
SQL.column('registrations', 'groupId')
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
.join(
|
|
63
|
+
SQL.join(
|
|
64
|
+
SQL.table('organizations')
|
|
65
|
+
).where(
|
|
66
|
+
SQL.column('organizations', 'id'),
|
|
67
|
+
SQL.column('registrations', 'organizationId')
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
.where(
|
|
71
|
+
SQL.column('memberId'),
|
|
72
|
+
SQL.column('members', 'id'),
|
|
73
|
+
).whereNot(
|
|
74
|
+
SQL.column('registeredAt'),
|
|
75
|
+
null,
|
|
76
|
+
).where(
|
|
77
|
+
SQL.column('deactivatedAt'),
|
|
78
|
+
null,
|
|
79
|
+
).where(
|
|
80
|
+
SQL.column('groups', 'deletedAt'),
|
|
81
|
+
null
|
|
82
|
+
),
|
|
83
|
+
{
|
|
84
|
+
...registrationFilterCompilers,
|
|
85
|
+
"organization": createSQLFilterNamespace(organizationFilterCompilers)
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
|
|
89
|
+
responsibilities: createSQLRelationFilterCompiler(
|
|
90
|
+
SQL.select()
|
|
91
|
+
.from(
|
|
92
|
+
SQL.table('member_responsibility_records')
|
|
93
|
+
)
|
|
94
|
+
.join(
|
|
95
|
+
SQL.leftJoin(
|
|
96
|
+
SQL.table('groups')
|
|
97
|
+
).where(
|
|
98
|
+
SQL.column('groups', 'id'),
|
|
99
|
+
SQL.column('member_responsibility_records', 'groupId')
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
.where(
|
|
103
|
+
SQL.column('memberId'),
|
|
104
|
+
SQL.column('members', 'id'),
|
|
105
|
+
),
|
|
106
|
+
{
|
|
107
|
+
...baseSQLFilterCompilers,
|
|
108
|
+
// Alias for responsibilityId
|
|
109
|
+
"id": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
|
|
110
|
+
"responsibilityId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'responsibilityId')),
|
|
111
|
+
"organizationId": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'organizationId')),
|
|
112
|
+
"startDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'startDate')),
|
|
113
|
+
"endDate": createSQLColumnFilterCompiler(SQL.column('member_responsibility_records', 'endDate')),
|
|
114
|
+
"group": createSQLFilterNamespace({
|
|
115
|
+
...baseSQLFilterCompilers,
|
|
116
|
+
id: createSQLColumnFilterCompiler(SQL.column('groups', 'id')),
|
|
117
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId')),
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
|
|
122
|
+
platformMemberships: createSQLRelationFilterCompiler(
|
|
123
|
+
SQL.select()
|
|
124
|
+
.from(
|
|
125
|
+
SQL.table('member_platform_memberships')
|
|
126
|
+
)
|
|
127
|
+
.where(
|
|
128
|
+
SQL.column('memberId'),
|
|
129
|
+
SQL.column('members', 'id'),
|
|
130
|
+
)
|
|
131
|
+
.where(
|
|
132
|
+
SQL.column('deletedAt'),
|
|
133
|
+
null,
|
|
134
|
+
),
|
|
135
|
+
{
|
|
136
|
+
...baseSQLFilterCompilers,
|
|
137
|
+
"id": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'id')),
|
|
138
|
+
"membershipTypeId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'membershipTypeId')),
|
|
139
|
+
"organizationId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'organizationId')),
|
|
140
|
+
"periodId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'periodId')),
|
|
141
|
+
"price": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'price')),
|
|
142
|
+
"invoiceId": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'invoiceId')),
|
|
143
|
+
"startDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'startDate')),
|
|
144
|
+
"endDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'endDate')),
|
|
145
|
+
"expireDate": createSQLColumnFilterCompiler(SQL.column('member_platform_memberships', 'expireDate')),
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
|
|
149
|
+
organizations: createSQLRelationFilterCompiler(
|
|
150
|
+
SQL.select()
|
|
151
|
+
.from(
|
|
152
|
+
SQL.table('registrations')
|
|
153
|
+
).join(
|
|
154
|
+
SQL.join(
|
|
155
|
+
SQL.table('groups')
|
|
156
|
+
).where(
|
|
157
|
+
SQL.column('groups', 'id'),
|
|
158
|
+
SQL.column('registrations', 'groupId')
|
|
159
|
+
)
|
|
160
|
+
).join(
|
|
161
|
+
SQL.join(
|
|
162
|
+
SQL.table('organizations')
|
|
163
|
+
).where(
|
|
164
|
+
SQL.column('organizations', 'id'),
|
|
165
|
+
SQL.column('registrations', 'organizationId')
|
|
166
|
+
)
|
|
167
|
+
).where(
|
|
168
|
+
SQL.column('memberId'),
|
|
169
|
+
SQL.column('members', 'id'),
|
|
170
|
+
).whereNot(
|
|
171
|
+
SQL.column('registeredAt'),
|
|
172
|
+
null,
|
|
173
|
+
).where(
|
|
174
|
+
SQL.column('groups', 'deletedAt'),
|
|
175
|
+
null
|
|
176
|
+
),
|
|
177
|
+
organizationFilterCompilers
|
|
178
|
+
),
|
|
179
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { SQL, SQLConcat, SQLFilterDefinitions, SQLNow, SQLNull, SQLScalar, SQLWhereEqual, SQLWhereOr, SQLWhereSign, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
|
|
2
|
+
|
|
3
|
+
export const organizationFilterCompilers: SQLFilterDefinitions = {
|
|
4
|
+
...baseSQLFilterCompilers,
|
|
5
|
+
id: createSQLExpressionFilterCompiler(
|
|
6
|
+
SQL.column('organizations', 'id')
|
|
7
|
+
),
|
|
8
|
+
uri: createSQLExpressionFilterCompiler(
|
|
9
|
+
SQL.column('organizations', 'uri')
|
|
10
|
+
),
|
|
11
|
+
name: createSQLExpressionFilterCompiler(
|
|
12
|
+
SQL.column('organizations', 'name')
|
|
13
|
+
),
|
|
14
|
+
active: createSQLExpressionFilterCompiler(
|
|
15
|
+
SQL.column('organizations', 'active')
|
|
16
|
+
),
|
|
17
|
+
city: createSQLExpressionFilterCompiler(
|
|
18
|
+
SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.city'),
|
|
19
|
+
{isJSONValue: true}
|
|
20
|
+
),
|
|
21
|
+
country: createSQLExpressionFilterCompiler(
|
|
22
|
+
SQL.jsonValue(SQL.column('organizations', 'address'), '$.value.country'),
|
|
23
|
+
{isJSONValue: true}
|
|
24
|
+
),
|
|
25
|
+
umbrellaOrganization: createSQLExpressionFilterCompiler(
|
|
26
|
+
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.umbrellaOrganization'),
|
|
27
|
+
{isJSONValue: true}
|
|
28
|
+
),
|
|
29
|
+
type: createSQLExpressionFilterCompiler(
|
|
30
|
+
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.type'),
|
|
31
|
+
{isJSONValue: true}
|
|
32
|
+
),
|
|
33
|
+
tags: createSQLExpressionFilterCompiler(
|
|
34
|
+
SQL.jsonValue(SQL.column('organizations', 'meta'), '$.value.tags'),
|
|
35
|
+
{isJSONValue: true, isJSONObject: true}
|
|
36
|
+
),
|
|
37
|
+
packages: createSQLRelationFilterCompiler(
|
|
38
|
+
SQL.select().from(
|
|
39
|
+
SQL.table('stamhoofd_packages')
|
|
40
|
+
).where(
|
|
41
|
+
SQL.column('organizationId'),
|
|
42
|
+
SQL.column('organizations', 'id'),
|
|
43
|
+
)
|
|
44
|
+
.where(
|
|
45
|
+
SQL.column('validAt'),
|
|
46
|
+
SQLWhereSign.NotEqual,
|
|
47
|
+
new SQLNull()
|
|
48
|
+
).where(
|
|
49
|
+
new SQLWhereOr([
|
|
50
|
+
new SQLWhereEqual(
|
|
51
|
+
SQL.column('validUntil'),
|
|
52
|
+
SQLWhereSign.Equal,
|
|
53
|
+
new SQLNull()
|
|
54
|
+
),
|
|
55
|
+
new SQLWhereEqual(
|
|
56
|
+
SQL.column('validUntil'),
|
|
57
|
+
SQLWhereSign.Greater,
|
|
58
|
+
new SQLNow()
|
|
59
|
+
)
|
|
60
|
+
])
|
|
61
|
+
).where(
|
|
62
|
+
new SQLWhereOr([
|
|
63
|
+
new SQLWhereEqual(
|
|
64
|
+
SQL.column('removeAt'),
|
|
65
|
+
SQLWhereSign.Equal,
|
|
66
|
+
new SQLNull()
|
|
67
|
+
),
|
|
68
|
+
new SQLWhereEqual(
|
|
69
|
+
SQL.column('removeAt'),
|
|
70
|
+
SQLWhereSign.Greater,
|
|
71
|
+
new SQLNow()
|
|
72
|
+
)
|
|
73
|
+
])
|
|
74
|
+
),
|
|
75
|
+
|
|
76
|
+
// const pack1 = await STPackage.where({ organizationId, validAt: { sign: "!=", value: null }, removeAt: { sign: ">", value: new Date() }})
|
|
77
|
+
// const pack2 = await STPackage.where({ organizationId, validAt: { sign: "!=", value: null }, removeAt: null })
|
|
78
|
+
{
|
|
79
|
+
...baseSQLFilterCompilers,
|
|
80
|
+
"type": createSQLExpressionFilterCompiler(
|
|
81
|
+
SQL.jsonValue(SQL.column('meta'), '$.value.type'),
|
|
82
|
+
{isJSONValue: true}
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
members: createSQLRelationFilterCompiler(
|
|
87
|
+
SQL.select().from(
|
|
88
|
+
SQL.table('members')
|
|
89
|
+
).join(
|
|
90
|
+
SQL.join(
|
|
91
|
+
SQL.table('registrations')
|
|
92
|
+
).where(
|
|
93
|
+
SQL.column('members', 'id'),
|
|
94
|
+
SQL.column('registrations', 'memberId')
|
|
95
|
+
)
|
|
96
|
+
).where(
|
|
97
|
+
SQL.column('registrations', 'organizationId'),
|
|
98
|
+
SQL.column('organizations', 'id'),
|
|
99
|
+
),
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
...baseSQLFilterCompilers,
|
|
103
|
+
name: createSQLExpressionFilterCompiler(
|
|
104
|
+
new SQLConcat(
|
|
105
|
+
SQL.column('firstName'),
|
|
106
|
+
new SQLScalar(' '),
|
|
107
|
+
SQL.column('lastName'),
|
|
108
|
+
)
|
|
109
|
+
),
|
|
110
|
+
"firstName": createSQLColumnFilterCompiler('firstName'),
|
|
111
|
+
"lastName": createSQLColumnFilterCompiler('lastName'),
|
|
112
|
+
"email": createSQLColumnFilterCompiler('email')
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { SQL, SQLCast, SQLConcat, SQLFilterDefinitions, SQLJsonUnquote, SQLScalar, baseSQLFilterCompilers, createSQLColumnFilterCompiler, createSQLExpressionFilterCompiler, createSQLFilterNamespace, createSQLRelationFilterCompiler } from "@stamhoofd/sql";
|
|
2
|
+
import { balanceItemPaymentsCompilers } from "./balance-item-payments";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Defines how to filter members in the database from StamhoofdFilter objects
|
|
6
|
+
*/
|
|
7
|
+
export const paymentFilterCompilers: SQLFilterDefinitions = {
|
|
8
|
+
...baseSQLFilterCompilers,
|
|
9
|
+
id: createSQLColumnFilterCompiler('id'),
|
|
10
|
+
method: createSQLColumnFilterCompiler('method'),
|
|
11
|
+
status: createSQLColumnFilterCompiler('status'),
|
|
12
|
+
organizationId: createSQLColumnFilterCompiler('organizationId'),
|
|
13
|
+
createdAt: createSQLColumnFilterCompiler('createdAt'),
|
|
14
|
+
updatedAt: createSQLColumnFilterCompiler('updatedAt'),
|
|
15
|
+
paidAt: createSQLColumnFilterCompiler('paidAt', {nullable: true}),
|
|
16
|
+
price: createSQLColumnFilterCompiler('price'),
|
|
17
|
+
provider: createSQLColumnFilterCompiler('provider', {nullable: true}),
|
|
18
|
+
customer: createSQLFilterNamespace({
|
|
19
|
+
...baseSQLFilterCompilers,
|
|
20
|
+
email: createSQLExpressionFilterCompiler(
|
|
21
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.email'),
|
|
22
|
+
{isJSONValue: true}
|
|
23
|
+
),
|
|
24
|
+
firstName: createSQLExpressionFilterCompiler(
|
|
25
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.firstName'),
|
|
26
|
+
{isJSONValue: true}
|
|
27
|
+
),
|
|
28
|
+
lastName: createSQLExpressionFilterCompiler(
|
|
29
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.lastName'),
|
|
30
|
+
{isJSONValue: true}
|
|
31
|
+
),
|
|
32
|
+
name: createSQLExpressionFilterCompiler(
|
|
33
|
+
new SQLCast(
|
|
34
|
+
new SQLConcat(
|
|
35
|
+
new SQLJsonUnquote(SQL.jsonValue(SQL.column('customer'), '$.value.firstName')),
|
|
36
|
+
new SQLScalar(' '),
|
|
37
|
+
new SQLJsonUnquote(SQL.jsonValue(SQL.column('customer'), '$.value.lastName')),
|
|
38
|
+
),
|
|
39
|
+
'CHAR'
|
|
40
|
+
)
|
|
41
|
+
),
|
|
42
|
+
company: createSQLFilterNamespace({
|
|
43
|
+
name: createSQLExpressionFilterCompiler(
|
|
44
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.name'),
|
|
45
|
+
{isJSONValue: true}
|
|
46
|
+
),
|
|
47
|
+
VATNumber: createSQLExpressionFilterCompiler(
|
|
48
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.VATNumber'),
|
|
49
|
+
{isJSONValue: true}
|
|
50
|
+
),
|
|
51
|
+
companyNumber: createSQLExpressionFilterCompiler(
|
|
52
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.companyNumber'),
|
|
53
|
+
{isJSONValue: true}
|
|
54
|
+
),
|
|
55
|
+
administrationEmail: createSQLExpressionFilterCompiler(
|
|
56
|
+
SQL.jsonValue(SQL.column('customer'), '$.value.company.administrationEmail'),
|
|
57
|
+
{isJSONValue: true}
|
|
58
|
+
),
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
balanceItemPayments: createSQLRelationFilterCompiler(
|
|
62
|
+
SQL.select()
|
|
63
|
+
.from(
|
|
64
|
+
SQL.table('balance_item_payments')
|
|
65
|
+
).join(
|
|
66
|
+
SQL.join(
|
|
67
|
+
SQL.table('balance_items')
|
|
68
|
+
).where(
|
|
69
|
+
SQL.column('balance_items', 'id'),
|
|
70
|
+
SQL.column('balance_item_payments', 'balanceItemId')
|
|
71
|
+
)
|
|
72
|
+
).where(
|
|
73
|
+
SQL.column('paymentId'),
|
|
74
|
+
SQL.column('payments', 'id')
|
|
75
|
+
),
|
|
76
|
+
balanceItemPaymentsCompilers
|
|
77
|
+
),
|
|
78
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SQLFilterDefinitions, baseSQLFilterCompilers, createSQLColumnFilterCompiler, SQL, createSQLFilterNamespace, createSQLExpressionFilterCompiler } from "@stamhoofd/sql";
|
|
2
|
+
|
|
3
|
+
export const registrationFilterCompilers: SQLFilterDefinitions = {
|
|
4
|
+
...baseSQLFilterCompilers,
|
|
5
|
+
"price": createSQLColumnFilterCompiler('price', {nullable: true}),
|
|
6
|
+
"pricePaid": createSQLColumnFilterCompiler('pricePaid'),
|
|
7
|
+
"canRegister": createSQLColumnFilterCompiler('canRegister'),
|
|
8
|
+
"organizationId": createSQLColumnFilterCompiler('organizationId'),
|
|
9
|
+
"groupId": createSQLColumnFilterCompiler('groupId'),
|
|
10
|
+
"registeredAt": createSQLColumnFilterCompiler('registeredAt', {nullable: true}),
|
|
11
|
+
"periodId": createSQLColumnFilterCompiler(SQL.column('registrations', 'periodId')),
|
|
12
|
+
|
|
13
|
+
"group": createSQLFilterNamespace({
|
|
14
|
+
...baseSQLFilterCompilers,
|
|
15
|
+
id: createSQLColumnFilterCompiler('groupId'),
|
|
16
|
+
name: createSQLExpressionFilterCompiler(
|
|
17
|
+
SQL.jsonValue(SQL.column('groups', 'settings'), '$.value.name')
|
|
18
|
+
),
|
|
19
|
+
status: createSQLExpressionFilterCompiler(
|
|
20
|
+
SQL.column('groups', 'status')
|
|
21
|
+
),
|
|
22
|
+
defaultAgeGroupId: createSQLColumnFilterCompiler(SQL.column('groups', 'defaultAgeGroupId'), {nullable: true}),
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { MemberWithRegistrations } from "@stamhoofd/models"
|
|
2
|
+
import { SQL, SQLOrderBy, SQLOrderByDirection, SQLSortDefinitions } from "@stamhoofd/sql"
|
|
3
|
+
import { Formatter } from "@stamhoofd/utility"
|
|
4
|
+
|
|
5
|
+
export const memberSorters: SQLSortDefinitions<MemberWithRegistrations> = {
|
|
6
|
+
'id': {
|
|
7
|
+
getValue(a) {
|
|
8
|
+
return a.id
|
|
9
|
+
},
|
|
10
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
11
|
+
return new SQLOrderBy({
|
|
12
|
+
column: SQL.column('id'),
|
|
13
|
+
direction
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
'name': {
|
|
18
|
+
getValue(a) {
|
|
19
|
+
return a.details.name
|
|
20
|
+
},
|
|
21
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
22
|
+
return SQLOrderBy.combine([
|
|
23
|
+
new SQLOrderBy({
|
|
24
|
+
column: SQL.column('firstName'),
|
|
25
|
+
direction
|
|
26
|
+
}),
|
|
27
|
+
new SQLOrderBy({
|
|
28
|
+
column: SQL.column('lastName'),
|
|
29
|
+
direction
|
|
30
|
+
})
|
|
31
|
+
])
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
'birthDay': {
|
|
35
|
+
getValue(a) {
|
|
36
|
+
return a.details.birthDay ? Formatter.dateIso(a.details.birthDay) : null
|
|
37
|
+
},
|
|
38
|
+
toSQL: (direction: SQLOrderByDirection): SQLOrderBy => {
|
|
39
|
+
return new SQLOrderBy({
|
|
40
|
+
column: SQL.column('birthDay'),
|
|
41
|
+
direction
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Note: never add mapped sortings, that should happen in the frontend -> e.g. map age to birthDay
|
|
46
|
+
}
|