bootpress 10.0.4 → 11.0.0-rc.2
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/index.d.ts +37 -33
- package/index.js +87 -241
- package/package.json +39 -38
package/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Request, Response } from "express";
|
|
2
|
-
import { ArraySchema, ErrorTemplateConfiguration,
|
|
3
|
-
|
|
4
|
-
type RequestHandler =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { ArraySchema, ErrorTemplateConfiguration, ExtendedTypeKeys, JsSchema, TypedSchema } from "./helpers";
|
|
3
|
+
|
|
4
|
+
type RequestHandler = {
|
|
5
|
+
(req: Request, res: Response): void
|
|
6
|
+
(req: Request, res: Response, next: NextFunction): void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type FunctionWithArgs<T = any> = (...args: any[]) => T;
|
|
10
|
+
type FunctionWithoutArgs<T = any> = () => T;
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
type RestedService<T extends Record<PropertyKey, any>> = {
|
|
14
14
|
[K in keyof T]:
|
|
@@ -19,31 +19,35 @@ type RestedService<T extends Record<PropertyKey, any>> = {
|
|
|
19
19
|
|
|
20
20
|
type InstanceOrClass<T extends Record<PropertyKey, any>> = T | (new () => T);
|
|
21
21
|
|
|
22
|
-
type TypeValueOf<S extends ExtendedTypeKeys | JsSchema | TypedSchema<JsSchema> | ArraySchema> =
|
|
23
|
-
S extends TypedSchema<JsSchema> ? S
|
|
24
|
-
: S extends ExtendedTypeKeys ? ExtValOf<S>
|
|
25
|
-
: S extends JsSchema ? TypedSchema<S>
|
|
26
|
-
: S extends ArraySchema ? TypedArraySchema<S>
|
|
27
|
-
: never;
|
|
28
|
-
|
|
29
22
|
declare function RestService<T extends Record<PropertyKey, any>>(service: InstanceOrClass<T>): RestedService<T>;
|
|
30
|
-
declare function RestMethod<T extends
|
|
23
|
+
declare function RestMethod<T extends FunctionWithoutArgs>(callback: T): RequestHandler;
|
|
24
|
+
declare function RestMethod<T extends FunctionWithArgs>(callback: T): (...args: Parameters<T>) => RequestHandler;
|
|
31
25
|
declare function Restify(target: any, key: PropertyKey, desc: PropertyDescriptor): PropertyDescriptor;
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
type BodyArgGetter = {
|
|
28
|
+
(type: ExtendedTypeKeys | JsSchema | TypedSchema<JsSchema> | ArraySchema): NeedsBuilder;
|
|
29
|
+
(type: ExtendedTypeKeys | JsSchema | TypedSchema<JsSchema> | ArraySchema, config: ErrorTemplateConfiguration): NeedsBuilder;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type NeedsBuilder = {
|
|
33
|
+
param: (name: string) => NeedsBuilder;
|
|
34
|
+
params: () => NeedsBuilder;
|
|
35
|
+
query: (name: string) => NeedsBuilder;
|
|
36
|
+
queries: () => NeedsBuilder;
|
|
37
|
+
body: (() => NeedsBuilder) | BodyArgGetter;
|
|
38
|
+
parseBody: BodyArgGetter;
|
|
39
|
+
cookie: (name: string) => NeedsBuilder;
|
|
40
|
+
cookies: () => NeedsBuilder;
|
|
41
|
+
header: (name: string) => NeedsBuilder;
|
|
42
|
+
headers: () => NeedsBuilder;
|
|
43
|
+
to: (handler: FunctionWithArgs) => RequestHandler;
|
|
44
|
+
}
|
|
45
|
+
declare function Needs(): NeedsBuilder;
|
|
44
46
|
|
|
45
47
|
export {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
RestMethod,
|
|
49
|
+
RestService,
|
|
50
|
+
Restify,
|
|
51
|
+
Needs
|
|
49
52
|
};
|
|
53
|
+
|
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { as, asStrict, log } = require("./helpers");
|
|
2
|
-
const { HttpError } = require("./types");
|
|
3
2
|
|
|
4
3
|
const protectedProperties = [
|
|
5
4
|
"toString",
|
|
@@ -36,7 +35,7 @@ function RestService(service) {
|
|
|
36
35
|
const value = keyvalue[1].value;
|
|
37
36
|
if (typeof value == "function" && !propertyName.startsWith("#")) {
|
|
38
37
|
newService[propertyName] = ((...args) =>
|
|
39
|
-
(
|
|
38
|
+
(_req, res) => {
|
|
40
39
|
try {
|
|
41
40
|
const result = service[propertyName](...args);
|
|
42
41
|
if (result == undefined) {
|
|
@@ -72,34 +71,35 @@ function RestService(service) {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
function RestMethod(callback) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return (...args) => handle(callback(...args));
|
|
74
|
+
return callback.length == 0
|
|
75
|
+
? handle(callback())
|
|
76
|
+
: (...args) => handle(callback(...args));
|
|
79
77
|
function handle(result) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
result
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
78
|
+
return (_req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
if (result == undefined) {
|
|
81
|
+
reply(res, 204, null);
|
|
82
|
+
} else if (result instanceof Promise) {
|
|
83
|
+
result.then((r) => {
|
|
84
|
+
if (r == undefined) {
|
|
85
|
+
reply(res, 204, undefined);
|
|
86
|
+
} else {
|
|
87
|
+
reply(res, r.status ?? 200, r.data ?? r);
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.catch((e) => {
|
|
91
|
+
const status = e.status ?? 500;
|
|
92
|
+
status === 500 && log.error(e.stack);
|
|
93
|
+
reply(res, status, e.message ?? e);
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
reply(res, result.status ?? 200, result.data ?? result);
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
const status = e.status ?? 500;
|
|
100
|
+
status === 500 && log.error(e.stack);
|
|
101
|
+
reply(res, status, e.message ?? e);
|
|
98
102
|
}
|
|
99
|
-
} catch (e) {
|
|
100
|
-
const status = e.status ?? 500;
|
|
101
|
-
status === 500 && log.error(e.stack);
|
|
102
|
-
reply(res, status, e.message ?? e);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -140,230 +140,76 @@ function Restify(target, key, desc) {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const paramToPass = req.params[paramName];
|
|
167
|
-
actualHandler(...args, paramToPass)(req, res);
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function PassQuery(searchQuery) {
|
|
175
|
-
return (actualHandler) => {
|
|
176
|
-
return (...args) => {
|
|
177
|
-
if (isRequstHandlerArgs(args)) {
|
|
178
|
-
const req = args.at(-3);
|
|
179
|
-
const res = args.at(-2);
|
|
180
|
-
const paramToPass = req.query[searchQuery];
|
|
181
|
-
return actualHandler(paramToPass)(req, res);
|
|
182
|
-
} else {
|
|
183
|
-
return (req, res) => {
|
|
184
|
-
const paramToPass = req.query[searchQuery];
|
|
185
|
-
actualHandler(...args, paramToPass)(req, res);
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function PassAllParams(actualHandler) {
|
|
193
|
-
return (...args) => {
|
|
194
|
-
if (isRequstHandlerArgs(args)) {
|
|
195
|
-
const req = args.at(-3);
|
|
196
|
-
const res = args.at(-2);
|
|
197
|
-
return actualHandler(req.params)(req, res);
|
|
198
|
-
} else {
|
|
199
|
-
return (req, res) => actualHandler(...args, req.params)(req, res);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function PassAllQueries(actualHandler) {
|
|
205
|
-
return (...args) => {
|
|
206
|
-
if (isRequstHandlerArgs(args)) {
|
|
207
|
-
const req = args.at(-3);
|
|
208
|
-
const res = args.at(-2);
|
|
209
|
-
return actualHandler(req.query)(req, res);
|
|
210
|
-
} else {
|
|
211
|
-
return (req, res) => actualHandler(...args, req.query)(req, res);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function ParseBodyAs(type, config = { messageTemplate: "Malformed Request Body\n{0}" }) {
|
|
217
|
-
return (actualHandler) => {
|
|
218
|
-
return (...args) => {
|
|
219
|
-
if (isRequstHandlerArgs(args)) {
|
|
220
|
-
const req = args.at(-3);
|
|
221
|
-
const res = args.at(-2);
|
|
222
|
-
try {
|
|
223
|
-
return actualHandler(as(req.body, type, config))(req, res);
|
|
224
|
-
} catch (e) {
|
|
225
|
-
const status = e.status ?? 500;
|
|
226
|
-
status === 500 && log.error(e.stack);
|
|
227
|
-
reply(res, status, e.message || e);
|
|
228
|
-
}
|
|
229
|
-
} else {
|
|
230
|
-
return (req, res) => {
|
|
231
|
-
try {
|
|
232
|
-
return actualHandler(...args, as(req.body, type, config))(req, res);
|
|
233
|
-
} catch (e) {
|
|
234
|
-
const status = e.status ?? 500;
|
|
235
|
-
status === 500 && log.error(e.stack);
|
|
236
|
-
reply(res, status, e.message || e);
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
};
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function PassBodyAs(type, config = { messageTemplate: "Malformed Request Body\n{0}" }) {
|
|
245
|
-
return (actualHandler) => {
|
|
246
|
-
return (...args) => {
|
|
247
|
-
if (isRequstHandlerArgs(args)) {
|
|
248
|
-
const req = args.at(-3);
|
|
249
|
-
const res = args.at(-2);
|
|
250
|
-
try {
|
|
251
|
-
if (req.body === null || req.body === undefined) throw new HttpError(400, "Request body is mandatory");
|
|
252
|
-
return actualHandler(asStrict(req.body, type, config))(req, res);
|
|
253
|
-
} catch (e) {
|
|
254
|
-
const status = e.status ?? 500;
|
|
255
|
-
status === 500 && log.error(e.stack);
|
|
256
|
-
reply(res, status, e.message || e);
|
|
257
|
-
}
|
|
143
|
+
function Needs() {
|
|
144
|
+
const argGetters = [];
|
|
145
|
+
|
|
146
|
+
const needs = {
|
|
147
|
+
param(name) {
|
|
148
|
+
argGetters.push((req) => req.params[name]);
|
|
149
|
+
return needs; // Return the object itself
|
|
150
|
+
},
|
|
151
|
+
params() {
|
|
152
|
+
argGetters.push((req) => req.params);
|
|
153
|
+
return needs;
|
|
154
|
+
},
|
|
155
|
+
query(name) {
|
|
156
|
+
argGetters.push((req) => req.query[name]);
|
|
157
|
+
return needs;
|
|
158
|
+
},
|
|
159
|
+
queries() {
|
|
160
|
+
argGetters.push((req) => req.query);
|
|
161
|
+
return needs;
|
|
162
|
+
},
|
|
163
|
+
body(type, config) {
|
|
164
|
+
if (type) {
|
|
165
|
+
argGetters.push((req) => asStrict(req.body || {}, type, config || { messageTemplate: "Malformed Request Body\n{0}" }));
|
|
258
166
|
} else {
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
if (req.body === null || req.body === undefined) throw new HttpError(400, "Request body is mandatory");
|
|
262
|
-
return actualHandler(...args, asStrict(req.body, type, config))(req, res);
|
|
263
|
-
} catch (e) {
|
|
264
|
-
const status = e.status ?? 500;
|
|
265
|
-
status === 500 && log.error(e.stack);
|
|
266
|
-
reply(res, status, e.message || e);
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function PassBody(actualHandler) {
|
|
275
|
-
return (...args) => {
|
|
276
|
-
if (isRequstHandlerArgs(args)) {
|
|
277
|
-
const req = args.at(-3);
|
|
278
|
-
const res = args.at(-2);
|
|
279
|
-
try {
|
|
280
|
-
return actualHandler(req.body)(req, res);
|
|
281
|
-
} catch (e) {
|
|
282
|
-
const status = e.status ?? 500;
|
|
283
|
-
status === 500 && log.error(e.stack);
|
|
284
|
-
reply(res, status, e.message || e);
|
|
167
|
+
argGetters.push((req) => req.body || {});
|
|
285
168
|
}
|
|
286
|
-
|
|
169
|
+
return needs;
|
|
170
|
+
},
|
|
171
|
+
parseBody(type, config) {
|
|
172
|
+
argGetters.push((req) => as(req.body || {}, type, config || { messageTemplate: "Malformed Request Body\n{0}" }));
|
|
173
|
+
return needs;
|
|
174
|
+
},
|
|
175
|
+
cookie(name) {
|
|
176
|
+
argGetters.push((req) => req.cookies[name]);
|
|
177
|
+
return needs;
|
|
178
|
+
},
|
|
179
|
+
cookies() {
|
|
180
|
+
argGetters.push((req) => req.cookies);
|
|
181
|
+
return needs;
|
|
182
|
+
},
|
|
183
|
+
header(name) {
|
|
184
|
+
argGetters.push((req) => req.headers[name]);
|
|
185
|
+
return needs;
|
|
186
|
+
},
|
|
187
|
+
headers() {
|
|
188
|
+
argGetters.push((req) => req.headers);
|
|
189
|
+
return needs;
|
|
190
|
+
},
|
|
191
|
+
request() {
|
|
192
|
+
argGetters.push((req) => req)
|
|
193
|
+
return needs;
|
|
194
|
+
},
|
|
195
|
+
response() {
|
|
196
|
+
argGetters.push((_, res) => res)
|
|
197
|
+
return needs;
|
|
198
|
+
},
|
|
199
|
+
to(callback) {
|
|
287
200
|
return (req, res) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
} catch (e) {
|
|
291
|
-
const status = e.status ?? 500;
|
|
292
|
-
status === 500 && log.error(e.stack);
|
|
293
|
-
reply(res, status, e.message || e);
|
|
294
|
-
}
|
|
201
|
+
const args = argGetters.map((getter) => getter(req, res));
|
|
202
|
+
return RestMethod(callback)(...args)(req, res);
|
|
295
203
|
};
|
|
296
204
|
}
|
|
297
205
|
};
|
|
298
|
-
}
|
|
299
206
|
|
|
300
|
-
|
|
301
|
-
return (...args) => {
|
|
302
|
-
if (isRequstHandlerArgs(args)) {
|
|
303
|
-
const req = args.at(-3);
|
|
304
|
-
const res = args.at(-2);
|
|
305
|
-
return actualHandler(req)(req, res);
|
|
306
|
-
} else {
|
|
307
|
-
return (req, res) => actualHandler(...args, req)(req, res);
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function PassResponse(actualHandler) {
|
|
313
|
-
return (...args) => {
|
|
314
|
-
if (isRequstHandlerArgs(args)) {
|
|
315
|
-
const req = args.at(-3);
|
|
316
|
-
const res = args.at(-2);
|
|
317
|
-
return actualHandler(res)(req, res);
|
|
318
|
-
} else {
|
|
319
|
-
return (req, res) => actualHandler(...args, res)(req, res);
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function PassAllCookies(actualHandler) {
|
|
325
|
-
return (...args) => {
|
|
326
|
-
if (isRequstHandlerArgs(args)) {
|
|
327
|
-
const req = args.at(-3);
|
|
328
|
-
const res = args.at(-2);
|
|
329
|
-
return actualHandler(req.cookies)(req, res);
|
|
330
|
-
} else {
|
|
331
|
-
return (req, res) => actualHandler(...args, req.cookies)(req, res);
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function PassCookie(cookieName) {
|
|
337
|
-
return (actualHandler) => {
|
|
338
|
-
return (...args) => {
|
|
339
|
-
if (isRequstHandlerArgs(args)) {
|
|
340
|
-
const req = args.at(-3);
|
|
341
|
-
const res = args.at(-2);
|
|
342
|
-
const cookie = req.cookies[cookieName];
|
|
343
|
-
return actualHandler(cookie)(req, res);
|
|
344
|
-
} else {
|
|
345
|
-
return (req, res) => {
|
|
346
|
-
const cookie = req.cookies[cookieName];
|
|
347
|
-
actualHandler(...args, cookie)(req, res);
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
};
|
|
207
|
+
return needs;
|
|
352
208
|
}
|
|
353
209
|
|
|
354
210
|
module.exports = {
|
|
355
211
|
RestService,
|
|
356
212
|
RestMethod,
|
|
357
213
|
Restify,
|
|
358
|
-
|
|
359
|
-
PassAllParams,
|
|
360
|
-
PassQuery,
|
|
361
|
-
PassAllQueries,
|
|
362
|
-
PassAllCookies,
|
|
363
|
-
PassCookie,
|
|
364
|
-
PassBody,
|
|
365
|
-
PassBodyAs,
|
|
366
|
-
ParseBodyAs,
|
|
367
|
-
PassRequest,
|
|
368
|
-
PassResponse,
|
|
214
|
+
Needs,
|
|
369
215
|
};
|
package/package.json
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "bootpress",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "REST service methods for express",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/ufukbakan/bootpress.git"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"bootpress",
|
|
12
|
-
"express",
|
|
13
|
-
"js",
|
|
14
|
-
"javascript",
|
|
15
|
-
"backend framework",
|
|
16
|
-
"backend",
|
|
17
|
-
"rest",
|
|
18
|
-
"service",
|
|
19
|
-
"methods",
|
|
20
|
-
"spring",
|
|
21
|
-
"boot",
|
|
22
|
-
"like"
|
|
23
|
-
],
|
|
24
|
-
"author": "Ufuk Bakan",
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"bugs": {
|
|
27
|
-
"url": "https://github.com/ufukbakan/bootpress/issues"
|
|
28
|
-
},
|
|
29
|
-
"homepage": "https://github.com/ufukbakan/bootpress#readme",
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"express": "^4.18.2"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@types/express": "^4.17.17",
|
|
35
|
-
"eslint": "^8.55.0",
|
|
36
|
-
"typescript": "^4.9.5"
|
|
37
|
-
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "bootpress",
|
|
3
|
+
"version": "11.0.0-rc.2",
|
|
4
|
+
"description": "REST service methods for express",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ufukbakan/bootpress.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"bootpress",
|
|
12
|
+
"express",
|
|
13
|
+
"js",
|
|
14
|
+
"javascript",
|
|
15
|
+
"backend framework",
|
|
16
|
+
"backend",
|
|
17
|
+
"rest",
|
|
18
|
+
"service",
|
|
19
|
+
"methods",
|
|
20
|
+
"spring",
|
|
21
|
+
"boot",
|
|
22
|
+
"like"
|
|
23
|
+
],
|
|
24
|
+
"author": "Ufuk Bakan",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/ufukbakan/bootpress/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/ufukbakan/bootpress#readme",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"express": "^4.18.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/express": "^4.17.17",
|
|
35
|
+
"eslint": "^8.55.0",
|
|
36
|
+
"typescript": "^4.9.5",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
}
|
|
39
|
+
}
|