@stamhoofd/backend 2.39.1 → 2.40.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/eslint.config.mjs +5 -0
- package/index.ts +81 -74
- package/jest.config.cjs +10 -0
- package/migrations.ts +16 -14
- package/package.json +11 -11
- package/src/crons/clear-excel-cache.test.ts +48 -50
- package/src/crons/clear-excel-cache.ts +18 -18
- package/src/crons/setup-steps.ts +2 -2
- package/src/crons.ts +325 -306
- package/src/decoders/StringArrayDecoder.ts +7 -7
- package/src/decoders/StringNullableDecoder.ts +1 -2
- package/src/email-recipient-loaders/members.ts +22 -22
- package/src/endpoints/admin/memberships/ChargeMembershipsEndpoint.ts +8 -9
- package/src/endpoints/admin/memberships/GetChargeMembershipsSummaryEndpoint.ts +39 -40
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +8 -8
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +44 -45
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +58 -57
- package/src/endpoints/auth/CreateAdminEndpoint.ts +48 -45
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +31 -31
- package/src/endpoints/auth/CreateTokenEndpoint.ts +146 -147
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +7 -7
- package/src/endpoints/auth/DeleteUserEndpoint.ts +15 -15
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +17 -18
- package/src/endpoints/auth/GetOtherUserEndpoint.ts +9 -10
- package/src/endpoints/auth/GetUserEndpoint.test.ts +32 -35
- package/src/endpoints/auth/GetUserEndpoint.ts +5 -6
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +35 -33
- package/src/endpoints/auth/PatchUserEndpoint.ts +55 -52
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +9 -9
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +8 -8
- package/src/endpoints/auth/SignupEndpoint.ts +37 -36
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +29 -28
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +33 -33
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +7 -7
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +37 -37
- package/src/endpoints/global/email/CreateEmailEndpoint.ts +30 -30
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +13 -13
- package/src/endpoints/global/email/GetEmailEndpoint.ts +13 -13
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +16 -16
- package/src/endpoints/global/email/PatchEmailEndpoint.ts +25 -25
- package/src/endpoints/global/events/GetEventsEndpoint.ts +43 -44
- package/src/endpoints/global/events/PatchEventsEndpoint.ts +127 -172
- package/src/endpoints/global/files/ExportToExcelEndpoint.ts +49 -50
- package/src/endpoints/global/files/GetFileCache.ts +13 -13
- package/src/endpoints/global/files/UploadFile.ts +51 -54
- package/src/endpoints/global/files/UploadImage.ts +53 -53
- package/src/endpoints/global/groups/GetGroupsEndpoint.ts +25 -25
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +24 -23
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +8 -8
- package/src/endpoints/global/members/GetMembersEndpoint.ts +105 -102
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +240 -239
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +12 -14
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +32 -33
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +48 -57
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +21 -22
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +28 -28
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +18 -18
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +20 -20
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +17 -17
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +81 -75
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +14 -14
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +11 -11
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +71 -68
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +27 -27
- package/src/endpoints/global/registration/GetUserBillingStatusEndpoint.ts +30 -30
- package/src/endpoints/global/registration/GetUserDetailedBillingStatusEndpoint.ts +34 -34
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +26 -26
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +12 -12
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +90 -90
- package/src/endpoints/global/registration/RegisterMembersEndpoint.test.ts +118 -121
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +362 -350
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +8 -9
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +21 -21
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +65 -65
- package/src/endpoints/organization/dashboard/billing/GetOrganizationBillingStatusEndpoint.ts +9 -9
- package/src/endpoints/organization/dashboard/billing/GetOrganizationDetailedBillingStatusEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +17 -17
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +21 -21
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +52 -52
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +37 -37
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +113 -112
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +29 -29
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +48 -47
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +22 -21
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +13 -14
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +12 -13
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +10 -12
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +14 -14
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +13 -13
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +120 -124
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +172 -173
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +88 -89
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +17 -17
- package/src/endpoints/organization/dashboard/payments/GetPaymentsCountEndpoint.ts +8 -8
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +66 -67
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +47 -47
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +93 -91
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +16 -17
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +170 -167
- package/src/endpoints/organization/dashboard/registration-periods/SetupStepReviewEndpoint.ts +25 -24
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +22 -23
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +22 -22
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +8 -9
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +17 -18
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +14 -15
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +19 -19
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +12 -12
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +103 -100
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +11 -12
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +15 -15
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +14 -14
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +23 -23
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +54 -52
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +84 -81
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +120 -111
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +24 -24
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +141 -130
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +25 -25
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +18 -18
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +36 -37
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +9 -9
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +11 -11
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +28 -27
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +20 -20
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +22 -22
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +14 -14
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +57 -56
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +65 -66
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +18 -17
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +124 -128
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +154 -145
- package/src/excel-loaders/members.ts +102 -103
- package/src/excel-loaders/payments.ts +155 -156
- package/src/helpers/AddressValidator.test.ts +32 -32
- package/src/helpers/AddressValidator.ts +128 -122
- package/src/helpers/AdminPermissionChecker.ts +339 -236
- package/src/helpers/AuthenticatedStructures.ts +233 -134
- package/src/helpers/BuckarooHelper.ts +134 -134
- package/src/helpers/CheckSettlements.ts +94 -88
- package/src/helpers/Context.ts +87 -86
- package/src/helpers/CookieHelper.ts +23 -22
- package/src/helpers/EmailResumer.ts +10 -10
- package/src/helpers/FileCache.ts +62 -62
- package/src/helpers/ForwardHandler.test.ts +122 -124
- package/src/helpers/ForwardHandler.ts +76 -70
- package/src/helpers/MemberUserSyncer.ts +101 -96
- package/src/helpers/MembershipCharger.ts +69 -69
- package/src/helpers/MembershipHelper.ts +11 -12
- package/src/helpers/OpenIDConnectHelper.ts +85 -82
- package/src/helpers/PeriodHelper.ts +65 -70
- package/src/helpers/StripeHelper.ts +146 -137
- package/src/helpers/StripePayoutChecker.ts +51 -52
- package/src/helpers/ViesHelper.ts +46 -44
- package/src/helpers/fetchToAsyncIterator.ts +14 -14
- package/src/helpers/xlsxAddressTransformerColumnFactory.ts +50 -52
- package/src/middleware/ContextMiddleware.ts +5 -5
- package/src/migrations/1646578856-validate-addresses.ts +6 -9
- package/src/seeds/0000000000-example.ts +3 -5
- package/src/seeds/1715028563-user-permissions.ts +16 -18
- package/src/seeds/1722256498-group-update-occupancy.ts +12 -12
- package/src/seeds/1722344162-sync-member-users.ts +14 -15
- package/src/seeds/1722344162-update-membership.ts +6 -6
- package/src/seeds/1726055544-balance-item-paid.ts +4 -4
- package/src/seeds/1726055545-balance-item-pending.ts +4 -4
- package/src/seeds/1726494419-update-cached-outstanding-balance.ts +16 -16
- package/src/seeds/1726494420-update-cached-outstanding-balance-from-items.ts +12 -12
- package/src/seeds/1726572303-schedule-stock-updates.ts +12 -12
- package/src/seeds/1726847064-setup-steps.ts +16 -0
- package/src/sql-filters/balance-item-payments.ts +7 -7
- package/src/sql-filters/events.ts +14 -14
- package/src/sql-filters/members.ts +96 -96
- package/src/sql-filters/organizations.ts +139 -75
- package/src/sql-filters/payments.ts +28 -28
- package/src/sql-filters/registrations.ts +14 -14
- package/src/sql-sorters/events.ts +25 -25
- package/src/sql-sorters/members.ts +26 -26
- package/src/sql-sorters/organizations.ts +36 -36
- package/src/sql-sorters/payments.ts +26 -26
- package/tests/e2e/stock.test.ts +616 -621
- package/tests/e2e/tickets.test.ts +255 -260
- package/tests/helpers/StripeMocker.ts +177 -179
- package/tests/helpers/TestServer.ts +9 -9
- package/tests/jest.global.setup.ts +14 -13
- package/tests/jest.setup.ts +33 -32
- package/.eslintrc.js +0 -61
- package/jest.config.js +0 -11
- package/src/helpers/SetupStepsUpdater.ts +0 -359
- package/src/seeds/1724076679-setup-steps.ts +0 -16
|
@@ -6,7 +6,7 @@ import { ArchiverWriterAdapter, exportToExcel, XlsxTransformerSheet, XlsxWriter
|
|
|
6
6
|
import { Platform, RateLimiter, sendEmailTemplate } from '@stamhoofd/models';
|
|
7
7
|
import { QueueHandler } from '@stamhoofd/queues';
|
|
8
8
|
import { EmailTemplateType, ExcelExportRequest, ExcelExportResponse, ExcelExportType, IPaginatedResponse, LimitedFilteredRequest, Replacement, Version } from '@stamhoofd/structures';
|
|
9
|
-
import { sleep } from
|
|
9
|
+
import { sleep } from '@stamhoofd/utility';
|
|
10
10
|
import { Context } from '../../../helpers/Context';
|
|
11
11
|
import { fetchToAsyncIterator } from '../../../helpers/fetchToAsyncIterator';
|
|
12
12
|
import { FileCache } from '../../../helpers/FileCache';
|
|
@@ -17,32 +17,32 @@ type Body = ExcelExportRequest;
|
|
|
17
17
|
type ResponseBody = ExcelExportResponse;
|
|
18
18
|
|
|
19
19
|
type ExcelExporter<T> = {
|
|
20
|
-
fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T[], LimitedFilteredRequest
|
|
21
|
-
sheets: XlsxTransformerSheet<T, unknown>[]
|
|
22
|
-
}
|
|
20
|
+
fetch(request: LimitedFilteredRequest): Promise<IPaginatedResponse<T[], LimitedFilteredRequest>>;
|
|
21
|
+
sheets: XlsxTransformerSheet<T, unknown>[];
|
|
22
|
+
};
|
|
23
23
|
|
|
24
24
|
export const limiter = new RateLimiter({
|
|
25
25
|
limits: [
|
|
26
|
-
{
|
|
26
|
+
{
|
|
27
27
|
// Max 200 per day
|
|
28
28
|
limit: 200,
|
|
29
|
-
duration: 60 * 1000 * 60 * 24
|
|
30
|
-
}
|
|
31
|
-
]
|
|
29
|
+
duration: 60 * 1000 * 60 * 24,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
35
|
-
bodyDecoder = ExcelExportRequest as Decoder<ExcelExportRequest
|
|
35
|
+
bodyDecoder = ExcelExportRequest as Decoder<ExcelExportRequest>;
|
|
36
36
|
|
|
37
37
|
// Other endpoints can register exports here
|
|
38
|
-
static loaders: Map<ExcelExportType, ExcelExporter<unknown>> = new Map()
|
|
38
|
+
static loaders: Map<ExcelExportType, ExcelExporter<unknown>> = new Map();
|
|
39
39
|
|
|
40
40
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
41
|
-
if (request.method
|
|
41
|
+
if (request.method !== 'POST') {
|
|
42
42
|
return [false];
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const params = Endpoint.parseParameters(request.url,
|
|
45
|
+
const params = Endpoint.parseParameters(request.url, '/export/excel/@type', { type: String });
|
|
46
46
|
|
|
47
47
|
if (params) {
|
|
48
48
|
return [true, params as Params];
|
|
@@ -52,33 +52,33 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
52
52
|
|
|
53
53
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
54
54
|
await Context.setOptionalOrganizationScope();
|
|
55
|
-
const {user} = await Context.authenticate()
|
|
55
|
+
const { user } = await Context.authenticate();
|
|
56
56
|
|
|
57
57
|
if (user.isApiUser) {
|
|
58
58
|
throw new SimpleError({
|
|
59
|
-
code:
|
|
60
|
-
message:
|
|
61
|
-
statusCode: 403
|
|
62
|
-
})
|
|
59
|
+
code: 'not_allowed',
|
|
60
|
+
message: 'API users are not allowed to export to Excel. The Excel export endpoint has a side effect of sending e-mails. Please use normal API endpoints to get the data you need.',
|
|
61
|
+
statusCode: 403,
|
|
62
|
+
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
if (QueueHandler.isRunning('user-export-to-excel-' + user.id)) {
|
|
66
66
|
throw new SimpleError({
|
|
67
|
-
code:
|
|
68
|
-
message:
|
|
67
|
+
code: 'not_allowed',
|
|
68
|
+
message: 'Export is pending',
|
|
69
69
|
human: 'Je hebt momenteel al een Excel export lopen. Wacht tot die klaar is voor je een nieuwe export start.',
|
|
70
|
-
statusCode: 403
|
|
71
|
-
})
|
|
70
|
+
statusCode: 403,
|
|
71
|
+
});
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const loader = ExportToExcelEndpoint.loaders.get(request.params.type as ExcelExportType);
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
if (!loader) {
|
|
77
77
|
throw new SimpleError({
|
|
78
|
-
code:
|
|
79
|
-
message:
|
|
80
|
-
statusCode: 400
|
|
81
|
-
})
|
|
78
|
+
code: 'invalid_type',
|
|
79
|
+
message: 'Invalid type ' + request.params.type,
|
|
80
|
+
statusCode: 400,
|
|
81
|
+
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
limiter.track(user.id, 1);
|
|
@@ -91,17 +91,16 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
91
91
|
if (sendEmail) {
|
|
92
92
|
await sendEmailTemplate(null, {
|
|
93
93
|
template: {
|
|
94
|
-
type: EmailTemplateType.ExcelExportSucceeded
|
|
94
|
+
type: EmailTemplateType.ExcelExportSucceeded,
|
|
95
95
|
},
|
|
96
96
|
recipients: [
|
|
97
97
|
user.createRecipient(Replacement.create({
|
|
98
98
|
token: 'downloadUrl',
|
|
99
|
-
value: url
|
|
100
|
-
}))
|
|
99
|
+
value: url,
|
|
100
|
+
})),
|
|
101
101
|
],
|
|
102
|
-
type: 'transactional'
|
|
103
|
-
})
|
|
104
|
-
|
|
102
|
+
type: 'transactional',
|
|
103
|
+
});
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
return url;
|
|
@@ -109,32 +108,32 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
109
108
|
if (sendEmail) {
|
|
110
109
|
await sendEmailTemplate(null, {
|
|
111
110
|
template: {
|
|
112
|
-
type: EmailTemplateType.ExcelExportFailed
|
|
111
|
+
type: EmailTemplateType.ExcelExportFailed,
|
|
113
112
|
},
|
|
114
113
|
recipients: [
|
|
115
|
-
user.createRecipient()
|
|
114
|
+
user.createRecipient(),
|
|
116
115
|
],
|
|
117
|
-
type: 'transactional'
|
|
118
|
-
})
|
|
116
|
+
type: 'transactional',
|
|
117
|
+
});
|
|
119
118
|
}
|
|
120
|
-
throw error
|
|
119
|
+
throw error;
|
|
121
120
|
}),
|
|
122
|
-
sleep(3000)
|
|
123
|
-
])
|
|
121
|
+
sleep(3000),
|
|
122
|
+
]);
|
|
124
123
|
|
|
125
124
|
if (typeof result === 'string') {
|
|
126
125
|
return new Response(ExcelExportResponse.create({
|
|
127
|
-
url: result
|
|
128
|
-
}))
|
|
126
|
+
url: result,
|
|
127
|
+
}));
|
|
129
128
|
}
|
|
130
|
-
|
|
129
|
+
|
|
131
130
|
// We'll send an e-mail
|
|
132
131
|
// Let the job know to send an e-mail when it is done
|
|
133
132
|
sendEmail = true;
|
|
134
133
|
|
|
135
134
|
return new Response(ExcelExportResponse.create({
|
|
136
|
-
url: null
|
|
137
|
-
}))
|
|
135
|
+
url: null,
|
|
136
|
+
}));
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
async job(loader: ExcelExporter<unknown>, request: ExcelExportRequest, type: string): Promise<string> {
|
|
@@ -145,7 +144,7 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
145
144
|
// Estimate how long it will take.
|
|
146
145
|
// If too long, we'll schedule it and write it to Digitalocean Spaces
|
|
147
146
|
// Otherwise we'll just return the file directly
|
|
148
|
-
const {file, stream} = await FileCache.getWriteStream('.xlsx');
|
|
147
|
+
const { file, stream } = await FileCache.getWriteStream('.xlsx');
|
|
149
148
|
|
|
150
149
|
const zipWriterAdapter = new ArchiverWriterAdapter(stream);
|
|
151
150
|
const writer = new XlsxWriter(zipWriterAdapter);
|
|
@@ -157,14 +156,14 @@ export class ExportToExcelEndpoint extends Endpoint<Params, Query, Body, Respons
|
|
|
157
156
|
definitions: loader.sheets,
|
|
158
157
|
writer,
|
|
159
158
|
dataGenerator: fetchToAsyncIterator(request.filter, loader),
|
|
160
|
-
filter: request.workbookFilter
|
|
161
|
-
})
|
|
159
|
+
filter: request.workbookFilter,
|
|
160
|
+
});
|
|
162
161
|
|
|
163
|
-
console.log('Done writing excel file')
|
|
162
|
+
console.log('Done writing excel file');
|
|
164
163
|
|
|
165
|
-
const url = 'https://'+ STAMHOOFD.domains.api + '/v'+ Version +'/file-cache?file=' + encodeURIComponent(file) + '&name=' + encodeURIComponent(type)
|
|
164
|
+
const url = 'https://' + STAMHOOFD.domains.api + '/v' + Version + '/file-cache?file=' + encodeURIComponent(file) + '&name=' + encodeURIComponent(type);
|
|
166
165
|
return url;
|
|
167
|
-
}, 2)
|
|
166
|
+
}, 2);
|
|
168
167
|
});
|
|
169
168
|
}
|
|
170
169
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
1
|
import { AutoEncoder, Decoder, field, StringDecoder } from '@simonbackx/simple-encoding';
|
|
3
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
3
|
import { Readable } from 'node:stream';
|
|
@@ -9,11 +8,11 @@ import { RateLimiter } from '@stamhoofd/models';
|
|
|
9
8
|
|
|
10
9
|
type Params = Record<never, never>;
|
|
11
10
|
class Query extends AutoEncoder {
|
|
12
|
-
@field({ decoder: StringDecoder})
|
|
13
|
-
file: string
|
|
11
|
+
@field({ decoder: StringDecoder })
|
|
12
|
+
file: string;
|
|
14
13
|
|
|
15
14
|
@field({ decoder: StringDecoder, optional: true })
|
|
16
|
-
name?: string
|
|
15
|
+
name?: string;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
type Body = undefined;
|
|
@@ -21,23 +20,23 @@ type ResponseBody = Readable;
|
|
|
21
20
|
|
|
22
21
|
export const limiter = new RateLimiter({
|
|
23
22
|
limits: [
|
|
24
|
-
{
|
|
23
|
+
{
|
|
25
24
|
// Max 200 per day
|
|
26
25
|
limit: 200,
|
|
27
|
-
duration: 60 * 1000 * 60 * 24
|
|
28
|
-
}
|
|
29
|
-
]
|
|
26
|
+
duration: 60 * 1000 * 60 * 24,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
export class GetFileCache extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
33
|
-
queryDecoder = Query as Decoder<Query
|
|
32
|
+
queryDecoder = Query as Decoder<Query>;
|
|
34
33
|
|
|
35
34
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
36
|
-
if (request.method
|
|
35
|
+
if (request.method !== 'GET') {
|
|
37
36
|
return [false];
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
const params = Endpoint.parseParameters(request.url,
|
|
39
|
+
const params = Endpoint.parseParameters(request.url, '/file-cache', {});
|
|
41
40
|
|
|
42
41
|
if (params) {
|
|
43
42
|
return [true, params as Params];
|
|
@@ -51,7 +50,7 @@ export class GetFileCache extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
51
50
|
limiter.track(request.request.getIP(), 1);
|
|
52
51
|
|
|
53
52
|
// Return readable stream
|
|
54
|
-
const {stream, contentLength, extension} = await FileCache.read(request.query.file, 1);
|
|
53
|
+
const { stream, contentLength, extension } = await FileCache.read(request.query.file, 1);
|
|
55
54
|
|
|
56
55
|
const response = new Response(stream);
|
|
57
56
|
response.headers['Content-Type'] = 'application/octet-stream';
|
|
@@ -59,7 +58,8 @@ export class GetFileCache extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
59
58
|
if (request.query.name) {
|
|
60
59
|
const slug = Formatter.fileSlug(request.query.name) + extension;
|
|
61
60
|
response.headers['Content-Disposition'] = `attachment; filename="${slug}"`;
|
|
62
|
-
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
63
|
response.headers['Content-Disposition'] = `attachment; filename="bestand${extension}"`;
|
|
64
64
|
}
|
|
65
65
|
response.headers['Content-Length'] = contentLength.toString();
|
|
@@ -1,46 +1,44 @@
|
|
|
1
|
-
|
|
2
1
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
3
2
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
3
|
import { File } from '@stamhoofd/structures';
|
|
5
4
|
import { Formatter } from '@stamhoofd/utility';
|
|
6
5
|
import AWS from 'aws-sdk';
|
|
7
6
|
import formidable from 'formidable';
|
|
8
|
-
import { promises as fs } from
|
|
9
|
-
import { v4 as uuidv4 } from
|
|
7
|
+
import { promises as fs } from 'fs';
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
9
|
|
|
11
10
|
import { Context } from '../../../helpers/Context';
|
|
12
11
|
import { limiter } from './UploadImage';
|
|
13
12
|
|
|
14
13
|
type Params = Record<string, never>;
|
|
15
|
-
type Query =
|
|
16
|
-
type Body = undefined
|
|
17
|
-
type ResponseBody = File
|
|
18
|
-
|
|
14
|
+
type Query = Record<string, never>;
|
|
15
|
+
type Body = undefined;
|
|
16
|
+
type ResponseBody = File;
|
|
19
17
|
|
|
20
18
|
interface FormidableFile {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
// The size of the uploaded file in bytes.
|
|
20
|
+
// If the file is still being uploaded (see `'fileBegin'` event),
|
|
21
|
+
// this property says how many bytes of the file have been written to disk yet.
|
|
22
|
+
size: number;
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// The path this file is being written to. You can modify this in the `'fileBegin'` event in
|
|
25
|
+
// case you are unhappy with the way formidable generates a temporary path for your files.
|
|
26
|
+
filepath: string;
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
// The name this file had according to the uploading client.
|
|
29
|
+
originalFilename: string | null;
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
// The mime type of this file, according to the uploading client.
|
|
32
|
+
mimetype: string | null;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
35
|
+
export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
38
36
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
39
|
-
if (request.method
|
|
37
|
+
if (request.method !== 'POST') {
|
|
40
38
|
return [false];
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
const params = Endpoint.parseParameters(request.url,
|
|
41
|
+
const params = Endpoint.parseParameters(request.url, '/upload-file', {});
|
|
44
42
|
|
|
45
43
|
if (params) {
|
|
46
44
|
return [true, params as Params];
|
|
@@ -50,23 +48,23 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
53
|
-
await Context.setOptionalOrganizationScope()
|
|
54
|
-
const {user} = await Context.authenticate();
|
|
51
|
+
await Context.setOptionalOrganizationScope();
|
|
52
|
+
const { user } = await Context.authenticate();
|
|
55
53
|
|
|
56
54
|
if (!Context.auth.canUpload()) {
|
|
57
|
-
throw Context.auth.error()
|
|
55
|
+
throw Context.auth.error();
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
|
|
61
59
|
throw new SimpleError({
|
|
62
|
-
code:
|
|
63
|
-
message:
|
|
64
|
-
statusCode: 503
|
|
65
|
-
})
|
|
60
|
+
code: 'not_available',
|
|
61
|
+
message: 'This endpoint is temporarily not available',
|
|
62
|
+
statusCode: 503,
|
|
63
|
+
});
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
if (!request.request.request) {
|
|
69
|
-
throw new Error(
|
|
67
|
+
throw new Error('Not supported without real request');
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
limiter.track(user.id, 1);
|
|
@@ -75,9 +73,9 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
75
73
|
const file = await new Promise<FormidableFile>((resolve, reject) => {
|
|
76
74
|
if (!request.request.request) {
|
|
77
75
|
reject(new SimpleError({
|
|
78
|
-
code:
|
|
79
|
-
message:
|
|
80
|
-
statusCode: 500
|
|
76
|
+
code: 'invalid_request',
|
|
77
|
+
message: 'Invalid request',
|
|
78
|
+
statusCode: 500,
|
|
81
79
|
}));
|
|
82
80
|
return;
|
|
83
81
|
}
|
|
@@ -87,26 +85,25 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
87
85
|
return;
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
if (!files.file || !Array.isArray(files.file) || files.file.length !== 1){
|
|
88
|
+
if (!files.file || !Array.isArray(files.file) || files.file.length !== 1) {
|
|
91
89
|
reject(new SimpleError({
|
|
92
|
-
code:
|
|
93
|
-
message:
|
|
94
|
-
field:
|
|
95
|
-
}))
|
|
90
|
+
code: 'missing_field',
|
|
91
|
+
message: 'Missing file',
|
|
92
|
+
field: 'file',
|
|
93
|
+
}));
|
|
96
94
|
return;
|
|
97
95
|
}
|
|
98
|
-
|
|
96
|
+
|
|
99
97
|
resolve(files.file[0]);
|
|
100
98
|
});
|
|
101
99
|
});
|
|
102
100
|
|
|
103
|
-
|
|
104
101
|
if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
|
|
105
102
|
throw new SimpleError({
|
|
106
|
-
code:
|
|
107
|
-
message:
|
|
108
|
-
statusCode: 503
|
|
109
|
-
})
|
|
103
|
+
code: 'not_available',
|
|
104
|
+
message: 'Uploading is not available',
|
|
105
|
+
statusCode: 503,
|
|
106
|
+
});
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
const fileContent = await fs.readFile(file.filepath);
|
|
@@ -114,33 +111,33 @@ export class UploadFile extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
114
111
|
const s3 = new AWS.S3({
|
|
115
112
|
endpoint: STAMHOOFD.SPACES_ENDPOINT,
|
|
116
113
|
accessKeyId: STAMHOOFD.SPACES_KEY,
|
|
117
|
-
secretAccessKey: STAMHOOFD.SPACES_SECRET
|
|
114
|
+
secretAccessKey: STAMHOOFD.SPACES_SECRET,
|
|
118
115
|
});
|
|
119
116
|
|
|
120
|
-
let prefix = (STAMHOOFD.SPACES_PREFIX ??
|
|
117
|
+
let prefix = (STAMHOOFD.SPACES_PREFIX ?? '');
|
|
121
118
|
if (prefix.length > 0) {
|
|
122
|
-
prefix +=
|
|
119
|
+
prefix += '/';
|
|
123
120
|
}
|
|
124
|
-
|
|
121
|
+
|
|
125
122
|
// Also include the source, in private mode
|
|
126
123
|
const fileId = uuidv4();
|
|
127
|
-
const uploadExt = file.mimetype ==
|
|
128
|
-
const filenameWithoutExt = file.originalFilename?.split(
|
|
129
|
-
const key = prefix+(STAMHOOFD.environment ??
|
|
124
|
+
const uploadExt = file.mimetype == 'application/pdf' ? 'pdf' : 'pdf';
|
|
125
|
+
const filenameWithoutExt = file.originalFilename?.split('.').slice(0, -1).join('.') ?? fileId;
|
|
126
|
+
const key = prefix + (STAMHOOFD.environment ?? 'development') + '/' + fileId + '/' + (Formatter.slug(filenameWithoutExt) + '.' + uploadExt);
|
|
130
127
|
const params = {
|
|
131
128
|
Bucket: STAMHOOFD.SPACES_BUCKET,
|
|
132
129
|
Key: key,
|
|
133
130
|
Body: fileContent, // TODO
|
|
134
|
-
ContentType: file.mimetype ??
|
|
135
|
-
ACL:
|
|
131
|
+
ContentType: file.mimetype ?? 'application/pdf',
|
|
132
|
+
ACL: 'public-read',
|
|
136
133
|
};
|
|
137
134
|
|
|
138
135
|
const fileStruct = new File({
|
|
139
136
|
id: fileId,
|
|
140
|
-
server:
|
|
137
|
+
server: 'https://' + STAMHOOFD.SPACES_BUCKET + '.' + STAMHOOFD.SPACES_ENDPOINT,
|
|
141
138
|
path: key,
|
|
142
139
|
size: fileContent.length,
|
|
143
|
-
name: file.originalFilename
|
|
140
|
+
name: file.originalFilename,
|
|
144
141
|
});
|
|
145
142
|
|
|
146
143
|
await s3.putObject(params).promise();
|
|
@@ -1,53 +1,52 @@
|
|
|
1
|
-
|
|
2
1
|
import { Decoder, ObjectData } from '@simonbackx/simple-encoding';
|
|
3
2
|
import { DecodedRequest, Endpoint, Request, Response } from '@simonbackx/simple-endpoints';
|
|
4
3
|
import { SimpleError } from '@simonbackx/simple-errors';
|
|
5
4
|
import { Image, RateLimiter } from '@stamhoofd/models';
|
|
6
5
|
import { Image as ImageStruct, ResolutionRequest } from '@stamhoofd/structures';
|
|
7
6
|
import formidable from 'formidable';
|
|
8
|
-
import { promises as fs } from
|
|
7
|
+
import { promises as fs } from 'fs';
|
|
9
8
|
|
|
10
9
|
import { Context } from '../../../helpers/Context';
|
|
11
10
|
|
|
12
11
|
type Params = Record<string, never>;
|
|
13
|
-
type Query =
|
|
14
|
-
type Body = undefined
|
|
15
|
-
type ResponseBody = ImageStruct
|
|
12
|
+
type Query = Record<string, never>;
|
|
13
|
+
type Body = undefined;
|
|
14
|
+
type ResponseBody = ImageStruct;
|
|
16
15
|
|
|
17
16
|
interface FormidableFile {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
// The size of the uploaded file in bytes.
|
|
18
|
+
// If the file is still being uploaded (see `'fileBegin'` event),
|
|
19
|
+
// this property says how many bytes of the file have been written to disk yet.
|
|
20
|
+
size: number;
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// The path this file is being written to. You can modify this in the `'fileBegin'` event in
|
|
23
|
+
// case you are unhappy with the way formidable generates a temporary path for your files.
|
|
24
|
+
filepath: string;
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// The name this file had according to the uploading client.
|
|
27
|
+
originalFilename: string | null;
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// The mime type of this file, according to the uploading client.
|
|
30
|
+
mimetype: string | null;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export const limiter = new RateLimiter({
|
|
35
34
|
limits: [
|
|
36
|
-
{
|
|
35
|
+
{
|
|
37
36
|
// Max 50 per hour
|
|
38
37
|
limit: 50,
|
|
39
|
-
duration: 60 * 1000 * 60
|
|
40
|
-
}
|
|
41
|
-
]
|
|
38
|
+
duration: 60 * 1000 * 60,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
42
41
|
});
|
|
43
42
|
|
|
44
|
-
export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
43
|
+
export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
45
44
|
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
46
|
-
if (request.method
|
|
45
|
+
if (request.method !== 'POST') {
|
|
47
46
|
return [false];
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
const params = Endpoint.parseParameters(request.url,
|
|
49
|
+
const params = Endpoint.parseParameters(request.url, '/upload-image', {});
|
|
51
50
|
|
|
52
51
|
if (params) {
|
|
53
52
|
return [true, params as Params];
|
|
@@ -57,39 +56,39 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
60
|
-
await Context.setOptionalOrganizationScope()
|
|
61
|
-
const {user} = await Context.authenticate();
|
|
59
|
+
await Context.setOptionalOrganizationScope();
|
|
60
|
+
const { user } = await Context.authenticate();
|
|
62
61
|
|
|
63
62
|
if (!Context.auth.canUpload()) {
|
|
64
|
-
throw Context.auth.error()
|
|
63
|
+
throw Context.auth.error();
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
if (!STAMHOOFD.SPACES_BUCKET || !STAMHOOFD.SPACES_ENDPOINT || !STAMHOOFD.SPACES_KEY || !STAMHOOFD.SPACES_SECRET) {
|
|
68
67
|
throw new SimpleError({
|
|
69
|
-
code:
|
|
70
|
-
message:
|
|
71
|
-
statusCode: 503
|
|
72
|
-
})
|
|
68
|
+
code: 'not_available',
|
|
69
|
+
message: 'This endpoint is temporarily not available',
|
|
70
|
+
statusCode: 503,
|
|
71
|
+
});
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
if (!request.request.request) {
|
|
76
|
-
throw new Error(
|
|
75
|
+
throw new Error('Not supported without real request');
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
limiter.track(user.id, 1);
|
|
80
79
|
|
|
81
|
-
const form = formidable({
|
|
82
|
-
maxFileSize: 5 * 1024 * 1024,
|
|
83
|
-
keepExtensions: true,
|
|
84
|
-
maxFiles: 1
|
|
80
|
+
const form = formidable({
|
|
81
|
+
maxFileSize: 5 * 1024 * 1024,
|
|
82
|
+
keepExtensions: true,
|
|
83
|
+
maxFiles: 1,
|
|
85
84
|
});
|
|
86
85
|
|
|
87
86
|
const [file, resolutions] = await new Promise<[FormidableFile, ResolutionRequest[]]>((resolve, reject) => {
|
|
88
87
|
if (!request.request.request) {
|
|
89
88
|
reject(new SimpleError({
|
|
90
|
-
code:
|
|
91
|
-
message:
|
|
92
|
-
statusCode: 500
|
|
89
|
+
code: 'invalid_request',
|
|
90
|
+
message: 'Invalid request',
|
|
91
|
+
statusCode: 500,
|
|
93
92
|
}));
|
|
94
93
|
return;
|
|
95
94
|
}
|
|
@@ -99,33 +98,34 @@ export class UploadImage extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
|
99
98
|
reject(err);
|
|
100
99
|
return;
|
|
101
100
|
}
|
|
102
|
-
if (!fields.resolutions || !Array.isArray(fields.resolutions) || fields.resolutions.length !== 1){
|
|
101
|
+
if (!fields.resolutions || !Array.isArray(fields.resolutions) || fields.resolutions.length !== 1) {
|
|
103
102
|
reject(new SimpleError({
|
|
104
|
-
code:
|
|
105
|
-
message:
|
|
106
|
-
field:
|
|
107
|
-
}))
|
|
103
|
+
code: 'missing_field',
|
|
104
|
+
message: 'Field resolutions is required',
|
|
105
|
+
field: 'resolutions',
|
|
106
|
+
}));
|
|
108
107
|
return;
|
|
109
108
|
}
|
|
110
|
-
if (!files.file || !Array.isArray(files.file) || files.file.length !== 1){
|
|
109
|
+
if (!files.file || !Array.isArray(files.file) || files.file.length !== 1) {
|
|
111
110
|
reject(new SimpleError({
|
|
112
|
-
code:
|
|
113
|
-
message:
|
|
114
|
-
field:
|
|
115
|
-
}))
|
|
111
|
+
code: 'missing_field',
|
|
112
|
+
message: 'Missing file',
|
|
113
|
+
field: 'file',
|
|
114
|
+
}));
|
|
116
115
|
return;
|
|
117
116
|
}
|
|
118
|
-
try {
|
|
119
|
-
const resolutions = new ObjectData(JSON.parse(fields.resolutions[0]), { version: request.request.getVersion() }).array(ResolutionRequest as Decoder<ResolutionRequest>)
|
|
117
|
+
try {
|
|
118
|
+
const resolutions = new ObjectData(JSON.parse(fields.resolutions[0]), { version: request.request.getVersion() }).array(ResolutionRequest as Decoder<ResolutionRequest>);
|
|
120
119
|
resolve([files.file[0], resolutions]);
|
|
121
|
-
}
|
|
122
|
-
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
reject(e);
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
const fileContent = await fs.readFile(file.filepath);
|
|
128
|
-
const image = await Image.create(fileContent, file.mimetype ?? undefined, resolutions)
|
|
128
|
+
const image = await Image.create(fileContent, file.mimetype ?? undefined, resolutions);
|
|
129
129
|
return new Response(ImageStruct.create(image));
|
|
130
130
|
}
|
|
131
131
|
}
|