@onebun/core 0.2.0 → 0.2.1
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/package.json +1 -1
- package/src/application/application.ts +188 -0
- package/src/decorators/decorators.test.ts +139 -0
- package/src/decorators/decorators.ts +115 -0
- package/src/docs-examples.test.ts +373 -73
- package/src/file/index.ts +8 -0
- package/src/file/onebun-file.test.ts +315 -0
- package/src/file/onebun-file.ts +304 -0
- package/src/index.ts +11 -0
- package/src/types.ts +27 -0
|
@@ -41,6 +41,85 @@ import type {
|
|
|
41
41
|
} from './types';
|
|
42
42
|
import type { ServerWebSocket } from 'bun';
|
|
43
43
|
|
|
44
|
+
import {
|
|
45
|
+
Controller,
|
|
46
|
+
Get,
|
|
47
|
+
Post,
|
|
48
|
+
Put,
|
|
49
|
+
Delete,
|
|
50
|
+
Patch,
|
|
51
|
+
Param,
|
|
52
|
+
Query,
|
|
53
|
+
Body,
|
|
54
|
+
Header,
|
|
55
|
+
Req,
|
|
56
|
+
Cookie,
|
|
57
|
+
Module,
|
|
58
|
+
Service,
|
|
59
|
+
BaseService,
|
|
60
|
+
BaseController,
|
|
61
|
+
UseMiddleware,
|
|
62
|
+
getServiceTag,
|
|
63
|
+
getControllerMetadata,
|
|
64
|
+
HttpStatusCode,
|
|
65
|
+
ParamType,
|
|
66
|
+
NotFoundError,
|
|
67
|
+
InternalServerError,
|
|
68
|
+
OneBunBaseError,
|
|
69
|
+
Env,
|
|
70
|
+
validate,
|
|
71
|
+
validateOrThrow,
|
|
72
|
+
MultiServiceApplication,
|
|
73
|
+
OneBunApplication,
|
|
74
|
+
createServiceDefinition,
|
|
75
|
+
createServiceClient,
|
|
76
|
+
WebSocketGateway,
|
|
77
|
+
BaseWebSocketGateway,
|
|
78
|
+
OnConnect,
|
|
79
|
+
OnDisconnect,
|
|
80
|
+
OnJoinRoom,
|
|
81
|
+
OnLeaveRoom,
|
|
82
|
+
OnMessage,
|
|
83
|
+
Client,
|
|
84
|
+
Socket,
|
|
85
|
+
MessageData,
|
|
86
|
+
RoomName,
|
|
87
|
+
PatternParams,
|
|
88
|
+
WsServer,
|
|
89
|
+
UseWsGuards,
|
|
90
|
+
WsAuthGuard,
|
|
91
|
+
WsPermissionGuard,
|
|
92
|
+
WsAnyPermissionGuard,
|
|
93
|
+
createGuard,
|
|
94
|
+
createInMemoryWsStorage,
|
|
95
|
+
SharedRedisProvider,
|
|
96
|
+
Sse,
|
|
97
|
+
getSseMetadata,
|
|
98
|
+
formatSseEvent,
|
|
99
|
+
createSseStream,
|
|
100
|
+
createWsServiceDefinition,
|
|
101
|
+
createWsClient,
|
|
102
|
+
createNativeWsClient,
|
|
103
|
+
matchPattern,
|
|
104
|
+
makeMockLoggerLayer,
|
|
105
|
+
hasOnModuleInit,
|
|
106
|
+
hasOnApplicationInit,
|
|
107
|
+
hasOnModuleDestroy,
|
|
108
|
+
hasBeforeApplicationDestroy,
|
|
109
|
+
hasOnApplicationDestroy,
|
|
110
|
+
callOnModuleInit,
|
|
111
|
+
callOnApplicationInit,
|
|
112
|
+
callOnModuleDestroy,
|
|
113
|
+
callBeforeApplicationDestroy,
|
|
114
|
+
callOnApplicationDestroy,
|
|
115
|
+
UploadedFile,
|
|
116
|
+
UploadedFiles,
|
|
117
|
+
FormField,
|
|
118
|
+
OneBunFile,
|
|
119
|
+
MimeType,
|
|
120
|
+
matchMimeType,
|
|
121
|
+
} from './';
|
|
122
|
+
|
|
44
123
|
|
|
45
124
|
/**
|
|
46
125
|
* @source docs/index.md#minimal-working-example
|
|
@@ -3421,79 +3500,6 @@ describe('WebSocket Chat Example (docs/examples/websocket-chat.md)', () => {
|
|
|
3421
3500
|
// SSE (Server-Sent Events) Documentation Tests
|
|
3422
3501
|
// ============================================================================
|
|
3423
3502
|
|
|
3424
|
-
import { Sse, getSseMetadata } from './decorators/decorators';
|
|
3425
|
-
import { formatSseEvent, createSseStream } from './module/controller';
|
|
3426
|
-
|
|
3427
|
-
import {
|
|
3428
|
-
Controller,
|
|
3429
|
-
Get,
|
|
3430
|
-
Post,
|
|
3431
|
-
Put,
|
|
3432
|
-
Delete,
|
|
3433
|
-
Patch,
|
|
3434
|
-
Param,
|
|
3435
|
-
Query,
|
|
3436
|
-
Body,
|
|
3437
|
-
Header,
|
|
3438
|
-
Req,
|
|
3439
|
-
Cookie,
|
|
3440
|
-
Module,
|
|
3441
|
-
Service,
|
|
3442
|
-
BaseService,
|
|
3443
|
-
BaseController,
|
|
3444
|
-
UseMiddleware,
|
|
3445
|
-
getServiceTag,
|
|
3446
|
-
getControllerMetadata,
|
|
3447
|
-
HttpStatusCode,
|
|
3448
|
-
ParamType,
|
|
3449
|
-
NotFoundError,
|
|
3450
|
-
InternalServerError,
|
|
3451
|
-
OneBunBaseError,
|
|
3452
|
-
Env,
|
|
3453
|
-
validate,
|
|
3454
|
-
validateOrThrow,
|
|
3455
|
-
MultiServiceApplication,
|
|
3456
|
-
OneBunApplication,
|
|
3457
|
-
createServiceDefinition,
|
|
3458
|
-
createServiceClient,
|
|
3459
|
-
WebSocketGateway,
|
|
3460
|
-
BaseWebSocketGateway,
|
|
3461
|
-
OnConnect,
|
|
3462
|
-
OnDisconnect,
|
|
3463
|
-
OnJoinRoom,
|
|
3464
|
-
OnLeaveRoom,
|
|
3465
|
-
OnMessage,
|
|
3466
|
-
Client,
|
|
3467
|
-
Socket,
|
|
3468
|
-
MessageData,
|
|
3469
|
-
RoomName,
|
|
3470
|
-
PatternParams,
|
|
3471
|
-
WsServer,
|
|
3472
|
-
UseWsGuards,
|
|
3473
|
-
WsAuthGuard,
|
|
3474
|
-
WsPermissionGuard,
|
|
3475
|
-
WsAnyPermissionGuard,
|
|
3476
|
-
createGuard,
|
|
3477
|
-
createInMemoryWsStorage,
|
|
3478
|
-
SharedRedisProvider,
|
|
3479
|
-
createWsServiceDefinition,
|
|
3480
|
-
createWsClient,
|
|
3481
|
-
createNativeWsClient,
|
|
3482
|
-
matchPattern,
|
|
3483
|
-
makeMockLoggerLayer,
|
|
3484
|
-
hasOnModuleInit,
|
|
3485
|
-
hasOnApplicationInit,
|
|
3486
|
-
hasOnModuleDestroy,
|
|
3487
|
-
hasBeforeApplicationDestroy,
|
|
3488
|
-
hasOnApplicationDestroy,
|
|
3489
|
-
callOnModuleInit,
|
|
3490
|
-
callOnApplicationInit,
|
|
3491
|
-
callOnModuleDestroy,
|
|
3492
|
-
callBeforeApplicationDestroy,
|
|
3493
|
-
callOnApplicationDestroy,
|
|
3494
|
-
} from './';
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
3503
|
describe('SSE (Server-Sent Events) API Documentation (docs/api/controllers.md)', () => {
|
|
3498
3504
|
describe('SseEvent Type (docs/api/controllers.md)', () => {
|
|
3499
3505
|
/**
|
|
@@ -4128,3 +4134,297 @@ describe('Working with Cookies (docs/api/controllers.md)', () => {
|
|
|
4128
4134
|
expect(AuthController).toBeDefined();
|
|
4129
4135
|
});
|
|
4130
4136
|
});
|
|
4137
|
+
|
|
4138
|
+
// ============================================================================
|
|
4139
|
+
// File Upload Documentation Tests
|
|
4140
|
+
// ============================================================================
|
|
4141
|
+
|
|
4142
|
+
describe('File Upload API Documentation (docs/api/decorators.md)', () => {
|
|
4143
|
+
/**
|
|
4144
|
+
* @source docs/api/decorators.md#uploadedfile
|
|
4145
|
+
*/
|
|
4146
|
+
describe('Single File Upload (docs/api/decorators.md#uploadedfile)', () => {
|
|
4147
|
+
it('should define controller with @UploadedFile decorator', () => {
|
|
4148
|
+
@Controller('/api/files')
|
|
4149
|
+
class FileController extends BaseController {
|
|
4150
|
+
@Post('/avatar')
|
|
4151
|
+
async uploadAvatar(
|
|
4152
|
+
@UploadedFile('avatar', {
|
|
4153
|
+
maxSize: 5 * 1024 * 1024,
|
|
4154
|
+
mimeTypes: [MimeType.ANY_IMAGE],
|
|
4155
|
+
}) file: OneBunFile,
|
|
4156
|
+
): Promise<Response> {
|
|
4157
|
+
await file.writeTo(`./uploads/${file.name}`);
|
|
4158
|
+
|
|
4159
|
+
return this.success({ filename: file.name, size: file.size });
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
expect(FileController).toBeDefined();
|
|
4164
|
+
const metadata = getControllerMetadata(FileController);
|
|
4165
|
+
expect(metadata).toBeDefined();
|
|
4166
|
+
expect(metadata!.routes).toHaveLength(1);
|
|
4167
|
+
|
|
4168
|
+
const route = metadata!.routes[0];
|
|
4169
|
+
expect(route.params).toBeDefined();
|
|
4170
|
+
expect(route.params!.length).toBe(1);
|
|
4171
|
+
expect(route.params![0].type).toBe(ParamType.FILE);
|
|
4172
|
+
expect(route.params![0].name).toBe('avatar');
|
|
4173
|
+
expect(route.params![0].isRequired).toBe(true);
|
|
4174
|
+
expect(route.params![0].fileOptions).toBeDefined();
|
|
4175
|
+
expect(route.params![0].fileOptions!.maxSize).toBe(5 * 1024 * 1024);
|
|
4176
|
+
expect(route.params![0].fileOptions!.mimeTypes).toEqual([MimeType.ANY_IMAGE]);
|
|
4177
|
+
});
|
|
4178
|
+
});
|
|
4179
|
+
|
|
4180
|
+
/**
|
|
4181
|
+
* @source docs/api/decorators.md#uploadedfiles
|
|
4182
|
+
*/
|
|
4183
|
+
describe('Multiple File Upload (docs/api/decorators.md#uploadedfiles)', () => {
|
|
4184
|
+
it('should define controller with @UploadedFiles decorator', () => {
|
|
4185
|
+
@Controller('/api/files')
|
|
4186
|
+
class FileController extends BaseController {
|
|
4187
|
+
@Post('/documents')
|
|
4188
|
+
async uploadDocs(
|
|
4189
|
+
@UploadedFiles('docs', { maxCount: 10 }) files: OneBunFile[],
|
|
4190
|
+
): Promise<Response> {
|
|
4191
|
+
for (const file of files) {
|
|
4192
|
+
await file.writeTo(`./uploads/${file.name}`);
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
return this.success({ count: files.length });
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
|
|
4199
|
+
expect(FileController).toBeDefined();
|
|
4200
|
+
const metadata = getControllerMetadata(FileController);
|
|
4201
|
+
expect(metadata).toBeDefined();
|
|
4202
|
+
|
|
4203
|
+
const route = metadata!.routes[0];
|
|
4204
|
+
expect(route.params).toBeDefined();
|
|
4205
|
+
expect(route.params![0].type).toBe(ParamType.FILES);
|
|
4206
|
+
expect(route.params![0].name).toBe('docs');
|
|
4207
|
+
expect(route.params![0].fileOptions!.maxCount).toBe(10);
|
|
4208
|
+
});
|
|
4209
|
+
|
|
4210
|
+
it('should support @UploadedFiles without field name (all files)', () => {
|
|
4211
|
+
@Controller('/api/files')
|
|
4212
|
+
class FileController extends BaseController {
|
|
4213
|
+
@Post('/batch')
|
|
4214
|
+
async uploadBatch(
|
|
4215
|
+
@UploadedFiles(undefined, { maxCount: 20 }) files: OneBunFile[],
|
|
4216
|
+
): Promise<Response> {
|
|
4217
|
+
return this.success({ count: files.length });
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
expect(FileController).toBeDefined();
|
|
4222
|
+
const metadata = getControllerMetadata(FileController);
|
|
4223
|
+
const route = metadata!.routes[0];
|
|
4224
|
+
expect(route.params![0].type).toBe(ParamType.FILES);
|
|
4225
|
+
expect(route.params![0].name).toBe('');
|
|
4226
|
+
});
|
|
4227
|
+
});
|
|
4228
|
+
|
|
4229
|
+
/**
|
|
4230
|
+
* @source docs/api/decorators.md#formfield
|
|
4231
|
+
*/
|
|
4232
|
+
describe('Form Field (docs/api/decorators.md#formfield)', () => {
|
|
4233
|
+
it('should define controller with @FormField decorator', () => {
|
|
4234
|
+
@Controller('/api/files')
|
|
4235
|
+
class FileController extends BaseController {
|
|
4236
|
+
@Post('/profile')
|
|
4237
|
+
async createProfile(
|
|
4238
|
+
@UploadedFile('avatar', { mimeTypes: [MimeType.ANY_IMAGE] }) avatar: OneBunFile,
|
|
4239
|
+
@FormField('name', { required: true }) name: string,
|
|
4240
|
+
@FormField('email') email: string,
|
|
4241
|
+
): Promise<Response> {
|
|
4242
|
+
await avatar.writeTo(`./uploads/${avatar.name}`);
|
|
4243
|
+
|
|
4244
|
+
return this.success({ name, email, avatar: avatar.name });
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
expect(FileController).toBeDefined();
|
|
4249
|
+
const metadata = getControllerMetadata(FileController);
|
|
4250
|
+
const route = metadata!.routes[0];
|
|
4251
|
+
expect(route.params).toBeDefined();
|
|
4252
|
+
expect(route.params!.length).toBe(3);
|
|
4253
|
+
|
|
4254
|
+
// @UploadedFile
|
|
4255
|
+
const fileParam = route.params!.find((p) => p.type === ParamType.FILE);
|
|
4256
|
+
expect(fileParam).toBeDefined();
|
|
4257
|
+
expect(fileParam!.name).toBe('avatar');
|
|
4258
|
+
|
|
4259
|
+
// @FormField (required)
|
|
4260
|
+
const nameParam = route.params!.find((p) => p.name === 'name');
|
|
4261
|
+
expect(nameParam).toBeDefined();
|
|
4262
|
+
expect(nameParam!.type).toBe(ParamType.FORM_FIELD);
|
|
4263
|
+
expect(nameParam!.isRequired).toBe(true);
|
|
4264
|
+
|
|
4265
|
+
// @FormField (optional)
|
|
4266
|
+
const emailParam = route.params!.find((p) => p.name === 'email');
|
|
4267
|
+
expect(emailParam).toBeDefined();
|
|
4268
|
+
expect(emailParam!.type).toBe(ParamType.FORM_FIELD);
|
|
4269
|
+
expect(emailParam!.isRequired).toBe(false);
|
|
4270
|
+
});
|
|
4271
|
+
});
|
|
4272
|
+
|
|
4273
|
+
/**
|
|
4274
|
+
* @source docs/api/decorators.md#onebunfile
|
|
4275
|
+
*/
|
|
4276
|
+
describe('OneBunFile Class (docs/api/decorators.md#onebunfile)', () => {
|
|
4277
|
+
it('should create OneBunFile from File and support all methods', async () => {
|
|
4278
|
+
const content = 'test file content';
|
|
4279
|
+
const file = new File([content], 'test.txt', { type: 'text/plain' });
|
|
4280
|
+
const oneBunFile = new OneBunFile(file);
|
|
4281
|
+
|
|
4282
|
+
expect(oneBunFile.name).toBe('test.txt');
|
|
4283
|
+
expect(oneBunFile.size).toBe(content.length);
|
|
4284
|
+
expect(oneBunFile.type).toStartWith('text/plain');
|
|
4285
|
+
|
|
4286
|
+
const base64 = await oneBunFile.toBase64();
|
|
4287
|
+
expect(base64).toBe(btoa(content));
|
|
4288
|
+
|
|
4289
|
+
const buffer = await oneBunFile.toBuffer();
|
|
4290
|
+
expect(buffer.toString()).toBe(content);
|
|
4291
|
+
|
|
4292
|
+
const blob = oneBunFile.toBlob();
|
|
4293
|
+
expect(blob.size).toBe(content.length);
|
|
4294
|
+
});
|
|
4295
|
+
|
|
4296
|
+
it('should create OneBunFile from base64', async () => {
|
|
4297
|
+
const content = 'base64 content';
|
|
4298
|
+
const base64 = btoa(content);
|
|
4299
|
+
const file = OneBunFile.fromBase64(base64, 'decoded.txt', 'text/plain');
|
|
4300
|
+
|
|
4301
|
+
expect(file.name).toBe('decoded.txt');
|
|
4302
|
+
expect(file.type).toStartWith('text/plain');
|
|
4303
|
+
|
|
4304
|
+
const roundTripped = await file.toBase64();
|
|
4305
|
+
expect(roundTripped).toBe(base64);
|
|
4306
|
+
});
|
|
4307
|
+
});
|
|
4308
|
+
|
|
4309
|
+
/**
|
|
4310
|
+
* @source docs/api/decorators.md#mimetype-enum
|
|
4311
|
+
*/
|
|
4312
|
+
describe('MimeType Enum (docs/api/decorators.md#mimetype-enum)', () => {
|
|
4313
|
+
it('should provide common MIME type constants', () => {
|
|
4314
|
+
// Wildcards
|
|
4315
|
+
expect(String(MimeType.ANY)).toBe('*/*');
|
|
4316
|
+
expect(String(MimeType.ANY_IMAGE)).toBe('image/*');
|
|
4317
|
+
expect(String(MimeType.ANY_VIDEO)).toBe('video/*');
|
|
4318
|
+
expect(String(MimeType.ANY_AUDIO)).toBe('audio/*');
|
|
4319
|
+
|
|
4320
|
+
// Specific types
|
|
4321
|
+
expect(String(MimeType.PNG)).toBe('image/png');
|
|
4322
|
+
expect(String(MimeType.PDF)).toBe('application/pdf');
|
|
4323
|
+
expect(String(MimeType.MP4)).toBe('video/mp4');
|
|
4324
|
+
|
|
4325
|
+
// Wildcard matching
|
|
4326
|
+
expect(matchMimeType('image/png', MimeType.ANY_IMAGE)).toBe(true);
|
|
4327
|
+
expect(matchMimeType('video/mp4', MimeType.ANY_IMAGE)).toBe(false);
|
|
4328
|
+
});
|
|
4329
|
+
});
|
|
4330
|
+
|
|
4331
|
+
/**
|
|
4332
|
+
* @source docs/api/decorators.md#json-base64-upload-format
|
|
4333
|
+
*/
|
|
4334
|
+
describe('JSON Base64 Upload (docs/api/decorators.md#json-base64-upload-format)', () => {
|
|
4335
|
+
it('should parse full JSON base64 format', () => {
|
|
4336
|
+
const base64 = btoa('png image data');
|
|
4337
|
+
const file = OneBunFile.fromBase64(base64, 'photo.png', 'image/png');
|
|
4338
|
+
|
|
4339
|
+
expect(file.name).toBe('photo.png');
|
|
4340
|
+
expect(file.type).toBe('image/png');
|
|
4341
|
+
});
|
|
4342
|
+
|
|
4343
|
+
it('should parse data URI format', () => {
|
|
4344
|
+
const base64 = btoa('svg data');
|
|
4345
|
+
const dataUri = `data:image/svg+xml;base64,${base64}`;
|
|
4346
|
+
const file = OneBunFile.fromBase64(dataUri, 'icon.svg');
|
|
4347
|
+
|
|
4348
|
+
expect(file.type).toBe('image/svg+xml');
|
|
4349
|
+
expect(file.name).toBe('icon.svg');
|
|
4350
|
+
});
|
|
4351
|
+
});
|
|
4352
|
+
});
|
|
4353
|
+
|
|
4354
|
+
describe('File Upload API Documentation (docs/api/controllers.md)', () => {
|
|
4355
|
+
/**
|
|
4356
|
+
* @source docs/api/controllers.md#single-file-upload
|
|
4357
|
+
*/
|
|
4358
|
+
it('should define single file upload controller', () => {
|
|
4359
|
+
@Controller('/api/files')
|
|
4360
|
+
class FileController extends BaseController {
|
|
4361
|
+
@Post('/avatar')
|
|
4362
|
+
async uploadAvatar(
|
|
4363
|
+
@UploadedFile('avatar', {
|
|
4364
|
+
maxSize: 5 * 1024 * 1024,
|
|
4365
|
+
mimeTypes: [MimeType.ANY_IMAGE],
|
|
4366
|
+
}) file: OneBunFile,
|
|
4367
|
+
): Promise<Response> {
|
|
4368
|
+
await file.writeTo(`./uploads/${file.name}`);
|
|
4369
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4370
|
+
const _base64 = await file.toBase64();
|
|
4371
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4372
|
+
const _buffer = await file.toBuffer();
|
|
4373
|
+
|
|
4374
|
+
return this.success({
|
|
4375
|
+
filename: file.name,
|
|
4376
|
+
size: file.size,
|
|
4377
|
+
type: file.type,
|
|
4378
|
+
});
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
expect(FileController).toBeDefined();
|
|
4383
|
+
});
|
|
4384
|
+
|
|
4385
|
+
/**
|
|
4386
|
+
* @source docs/api/controllers.md#multiple-file-upload
|
|
4387
|
+
*/
|
|
4388
|
+
it('should define multiple file upload controller', () => {
|
|
4389
|
+
@Controller('/api/files')
|
|
4390
|
+
class FileController extends BaseController {
|
|
4391
|
+
@Post('/documents')
|
|
4392
|
+
async uploadDocuments(
|
|
4393
|
+
@UploadedFiles('docs', {
|
|
4394
|
+
maxCount: 10,
|
|
4395
|
+
maxSize: 10 * 1024 * 1024,
|
|
4396
|
+
mimeTypes: [MimeType.PDF, MimeType.DOCX],
|
|
4397
|
+
}) files: OneBunFile[],
|
|
4398
|
+
): Promise<Response> {
|
|
4399
|
+
for (const file of files) {
|
|
4400
|
+
await file.writeTo(`./uploads/${file.name}`);
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
return this.success({ uploaded: files.length });
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4407
|
+
expect(FileController).toBeDefined();
|
|
4408
|
+
});
|
|
4409
|
+
|
|
4410
|
+
/**
|
|
4411
|
+
* @source docs/api/controllers.md#file-with-form-fields
|
|
4412
|
+
*/
|
|
4413
|
+
it('should define file with form fields controller', () => {
|
|
4414
|
+
@Controller('/api/files')
|
|
4415
|
+
class FileController extends BaseController {
|
|
4416
|
+
@Post('/profile')
|
|
4417
|
+
async createProfile(
|
|
4418
|
+
@UploadedFile('avatar', { mimeTypes: [MimeType.ANY_IMAGE] }) avatar: OneBunFile,
|
|
4419
|
+
@FormField('name', { required: true }) name: string,
|
|
4420
|
+
@FormField('email') email: string,
|
|
4421
|
+
): Promise<Response> {
|
|
4422
|
+
await avatar.writeTo(`./uploads/${avatar.name}`);
|
|
4423
|
+
|
|
4424
|
+
return this.success({ name, email, avatar: avatar.name });
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
expect(FileController).toBeDefined();
|
|
4429
|
+
});
|
|
4430
|
+
});
|