Skip to content

Commit 92062e5

Browse files
committed
- Added @Files decorator.
- Made some possible breaking changes to @Req. This will now target the ctx.request object and not ctx.req.
1 parent 5e2c48f commit 92062e5

File tree

10 files changed

+1955
-2247
lines changed

10 files changed

+1955
-2247
lines changed

.eslintrc.js

Lines changed: 0 additions & 14 deletions
This file was deleted.

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,18 @@ export class FooController {
260260
return body;
261261
}
262262

263+
@Post('/fieldsAndFiles')
264+
async createWithFiles(
265+
@Body({required: true}) body: FooCreateInput,
266+
@Files() files: Record<string,File>) {
267+
268+
// POST /api/v.../foo/orDie2
269+
270+
// Any file uploaded will appear in files, indexed by the name of the file.
271+
272+
return body;
273+
}
274+
263275

264276
@Delete('/:id')
265277
@Flow([aMiddleware, bMiddleware])
@@ -503,6 +515,11 @@ Injects the koa request object. useful when streaming data up to server
503515

504516
Injects the koa response object. useful when streaming data down to client.
505517

518+
#### @Files()
519+
520+
Injects the request files object. All files uploaded during the request can be found here, indexed by file field name.
521+
522+
506523
#### @Ctx()
507524

508525
Injects the whole koa context. For a more descriptive endpoint handler/action, avoid doing this if you can. Opt for more specific injections.

eslintrc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
root: true,
3+
parser: '@typescript-eslint/parser',
4+
plugins: [
5+
'@typescript-eslint',
6+
],
7+
extends: [
8+
'eslint:recommended',
9+
'plugin:@typescript-eslint/recommended',
10+
],
11+
};

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@hapi/boom": "^9.0.0",
3838
"class-transformer": "*",
3939
"class-validator": "*",
40-
"koa": "^2.x",
40+
"koa": "^2.x || ^3.x",
4141
"koa-body": "*",
4242
"koa-router": "*",
4343
"koa-session": "*",
@@ -53,15 +53,15 @@
5353
"@types/lodash": "^4.14.149",
5454
"@types/node": "^13.7.1",
5555
"@types/supertest": "^2.0.8",
56-
"@typescript-eslint/eslint-plugin": "^2.19.2",
57-
"@typescript-eslint/parser": "^2.19.2",
56+
"@typescript-eslint/eslint-plugin": "^4.28.2",
57+
"@typescript-eslint/parser": "^4.28.2",
5858
"class-validator": "^0.x",
59-
"eslint": "^6.8.0",
60-
"jest": "^25.1.0",
59+
"eslint": "^7.30.0",
60+
"jest": "^27.0.6",
6161
"supertest": "^4.0.2",
62-
"ts-jest": "^25.2.0",
62+
"ts-jest": "^27.0.3",
6363
"ts-node": "^8.6.2",
64-
"typescript": "^3.7.5",
64+
"typescript": "^4.3.5",
6565
"typescript-eslint": "0.0.1-alpha.0"
6666
}
6767
}

src/decorators.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,30 @@ export function Req(injectOptions?: string | Record<string, any>) {
223223
): void {
224224
addArgumentInjectMeta({
225225
index,
226-
injectSource: "req",
226+
injectSource: "request",
227227
injectOptions,
228228
methodName,
229229
object
230230
});
231231
};
232232
}
233233

234+
export function Files() {
235+
return function (
236+
object: Record<string, any>,
237+
methodName: string,
238+
index: number
239+
): void {
240+
addArgumentInjectMeta({
241+
index,
242+
injectSource: "request",
243+
injectOptions: "files",
244+
methodName,
245+
object
246+
});
247+
};
248+
}
249+
234250
export function Res(injectOptions?: string | Record<string, any>) {
235251
return function (
236252
object: Record<string, any>,

src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Koa from 'koa';
99
import Router from 'koa-router';
1010
import bodyParser from 'koa-body';
1111
import {addArgumentInjectMeta} from './util/tools';
12+
const unparsed = require('koa-body/unparsed.js');
1213

1314
export type KoaBodyOptions = {
1415
// Patch request body to Node's ctx.req, default false
@@ -264,9 +265,12 @@ export const bootstrapControllers = async (
264265
});
265266
}
266267

267-
//body parer
268+
//body parser
268269
if (options.bodyParser !== false) {
269-
app.use(bodyParser(options.bodyParser as KoaBodyOptions));
270+
app.use(bodyParser({...options.bodyParser as KoaBodyOptions,
271+
// includeUnparsed: true,
272+
multipart: true
273+
}));
270274
}
271275

272276
if (options.attachRoutes) {
@@ -292,6 +296,7 @@ export {
292296
Flow,
293297
Get,
294298
Header,
299+
Files,
295300
CurrentUser,
296301
Params,
297302
Patch,

src/tests/arguments.test.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ let nativeServer;
55
let testServer: request.SuperTest<request.Test>;
66
beforeAll(async () => {
77

8-
98
const {app, router} = await bootstrapControllers({
109
basePath: "/api",
1110
controllers: [__dirname + "/util/controllers/**/*.ts"],
@@ -49,16 +48,24 @@ describe("Arguments", () => {
4948

5049
describe("body", () => {
5150
it("whole object can be injected", async () => {
52-
const payload = { foo: "Ijebu garri is the best for soaking" };
51+
const payload = {foo: "Ijebu garri is the best for soaking"};
5352
const response = await testServer
5453
.post("/api/v2/arg/bodySimple")
5554
.send(payload)
5655
.expect(200);
5756
expect(response.body).toEqual(payload);
5857
});
5958

59+
it("can be set with field", async () => {
60+
const response = await testServer
61+
.post("/api/v2/arg/bodySimple")
62+
.field({abc: 123})
63+
.expect(200);
64+
expect(response.body.abc).toEqual("123");
65+
});
66+
6067
it("specific subfield can be injected", async () => {
61-
const payload = { foo: "Ijebu garri is the best for soaking" };
68+
const payload = {foo: "Ijebu garri is the best for soaking"};
6269
const response = await testServer
6370
.post("/api/v2/arg/bodySpecific")
6471
.send(payload)
@@ -178,15 +185,61 @@ describe("Arguments", () => {
178185
});
179186

180187
describe("req", () => {
181-
it("works", async () => {
188+
189+
it("can set headers", async () => {
182190
const response = await testServer
183191
.post("/api/v2/arg/req")
184192
.set("foo", "bar")
185193
.expect(200);
186194

187195
// returns a serialized req object
188-
expect(Buffer.isBuffer(response.body)).toEqual(true);
196+
// expect(Buffer.isBuffer(response.body)).toEqual(true);
197+
expect(response.body).toBeDefined();
198+
expect(response.body.foo).toEqual('bar');
189199
});
200+
201+
it("Buffer can be uploaded", async () => {
202+
const buffer = Buffer.from('some file data');
203+
const response = await testServer
204+
.post("/api/v2/arg/uploadBuffer")
205+
.set("foo", "bar")
206+
.attach('testFile', buffer)
207+
.expect(200);
208+
209+
// returns a serialized req object
210+
expect(response.body).toEqual({testFile: 'some file data'});
211+
});
212+
213+
it("File decorator injects files", async () => {
214+
215+
const response = await testServer
216+
.post("/api/v2/arg/uploadFile")
217+
.set("foo", "bar")
218+
.attach('testFile', 'src/tests/attachments/image.png')
219+
.expect(200);
220+
221+
// returns a serialized req object
222+
expect(response.body.testFile).toBeDefined();
223+
expect(response.body.testFile.name).toEqual('image.png');
224+
expect(response.body.testFile.type).toEqual('image/png');
225+
expect(response.body.testFile.size).toEqual(41569);
226+
});
227+
228+
it("Uploaded files can also be found in @Req.files", async () => {
229+
230+
const response = await testServer
231+
.post("/api/v2/arg/uploadFile2")
232+
.set("foo", "bar")
233+
.attach('testFile', 'src/tests/attachments/image.png')
234+
.expect(200);
235+
236+
// returns a serialized req object
237+
expect(response.body.testFile).toBeDefined();
238+
expect(response.body.testFile.name).toEqual('image.png');
239+
expect(response.body.testFile.type).toEqual('image/png');
240+
expect(response.body.testFile.size).toEqual(41569);
241+
});
242+
190243
});
191244

192245
describe("res", () => {

src/tests/attachments/image.png

40.6 KB
Loading

src/tests/util/controllers/ArgController.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
Body,
55
Controller,
66
Ctx,
7-
CurrentUser,
7+
CurrentUser, Files,
88
Flow,
99
Get,
1010
Header,
@@ -124,7 +124,23 @@ export class ArgController {
124124

125125
@Post('/req')
126126
async req(@Req() req: Request) {
127-
return req;
127+
128+
return req.header;
129+
}
130+
131+
@Post('/uploadBuffer')
132+
async uploadBuffer(@Ctx() ctx, @Req() req: Request) {
133+
return req.body;
134+
}
135+
136+
@Post('/uploadFile')
137+
async uploadFile(@Ctx() ctx, @Files() files: Record<string,File>) {
138+
return files;
139+
}
140+
141+
@Post('/uploadFile2')
142+
async uploadFile2(@Ctx() ctx, @Req() req: Request) {
143+
return req.files;
128144
}
129145

130146
@Post('/res')

0 commit comments

Comments
 (0)