├── .auto-changelog ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .lintstagedrc.json ├── .npmrc ├── README.md ├── cspell.json ├── eslint.config.js ├── lib ├── constants │ └── upload-file.constant.ts ├── decorators │ ├── enums.decorators.ts │ ├── id.decorator.ts │ ├── number.decorator.ts │ ├── pagination.decorator.ts │ ├── query-boolean.decorator.ts │ ├── query-strings.decorator.ts │ ├── real-ip.decorators.ts │ ├── require-upload-file.decorator.ts │ ├── require-upload-files.decorator.ts │ ├── sort.decorator.ts │ ├── sorts.decorator.ts │ ├── timestamp.decorator.ts │ ├── timezone.decorator.ts │ ├── user-agent.decorator.ts │ └── value.decorator.ts ├── filters │ └── file-upload.filter.ts ├── helpers │ ├── file.helper.ts │ ├── get-params.helper.ts │ └── sort.helper.ts ├── index.ts ├── interceptors │ ├── require-upload-file-interceptor.ts │ └── require-upload-files-interceptor.ts ├── interfaces │ ├── and-where-query.interface.ts │ └── custom-condition.interface.ts ├── types │ ├── column-type.type.ts │ ├── file-upload-configurations.type.ts │ ├── file-upload-options.type.ts │ ├── file-upload-params.type.ts │ ├── files-upload-params.type.ts │ ├── multer-options.type.ts │ ├── sort-direction.type.ts │ ├── sort-params.type.ts │ └── sorts-params.type.ts └── validators │ ├── exist-ids.validator.ts │ ├── exists.validator.ts │ └── unique.validator.ts ├── nest-cli.json ├── package-lock.json ├── package.json ├── prettier.config.js ├── renovate.json ├── sample ├── app.controller.ts ├── app.enum.ts ├── app.module.ts ├── i18n │ └── en │ │ ├── error.json │ │ └── validation.json └── main.ts ├── tsconfig.build.json └── tsconfig.json /.auto-changelog: -------------------------------------------------------------------------------- 1 | { 2 | "output": "CHANGELOG.md", 3 | "template": "keepachangelog", 4 | "unreleased": true, 5 | "ignoreCommitPattern": "^(?!(feat|fix|\\[feat\\]|\\[fix\\]))(.*)$", 6 | "commitLimit": false, 7 | "commitUrl": "https://github.com/hodfords-solutions/nestjs-base-decorator/commit/{id}" 8 | } -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | lint: 8 | uses: hodfords-solutions/actions/.github/workflows/lint.yaml@main 9 | build: 10 | uses: hodfords-solutions/actions/.github/workflows/publish.yaml@main 11 | with: 12 | build_path: dist/lib 13 | secrets: 14 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 15 | update-docs: 16 | uses: hodfords-solutions/actions/.github/workflows/update-doc.yaml@main 17 | needs: build 18 | secrets: 19 | DOC_SSH_PRIVATE_KEY: ${{ secrets.DOC_SSH_PRIVATE_KEY }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | .env -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib/**/*.ts": ["cspell", "prettier --write", "eslint --fix --max-warnings 0"] 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | always-auth=true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 | 5 |6 | Nestjs-Base-Decorator provides a collection of useful, ready-to-use custom decorators for NestJS, designed to enhance and simplify common functionality in your applications. These decorators will help you write cleaner and more maintainable code, reducing the need for repetitive boilerplate. 7 |
8 | 9 | ## Installation 🤖 10 | 11 | To begin using it, we first install the required dependencies. 12 | 13 | ``` 14 | npm install @hodfords/nestjs-base-decorator 15 | ``` 16 | 17 | ## Table of Contents 18 | 19 | - [Installation 🤖](#installation-) 20 | - [Table of Contents](#table-of-contents) 21 | - [Usage 🚀](#usage-) 22 | - [Param Decorators](#param-decorators) 23 | - [@EnumQuery()](#enumquery) 24 | - [@EnumsQuery()](#enumsquery) 25 | - [@Id()](#id) 26 | - [@Ids()](#ids) 27 | - [@Int()](#int) 28 | - [@Ints()](#ints) 29 | - [@Pagination()](#pagination) 30 | - [@QueryBoolean()](#queryboolean) 31 | - [@QueryStrings()](#querystrings) 32 | - [@RealIp()](#realip) 33 | - [@RequireToUploadFile()](#requiretouploadfile) 34 | - [@RequireToUploadFiles()](#requiretouploadfiles) 35 | - [@Sort()](#sort) 36 | - [@Sorts()](#sorts) 37 | - [@Timestamp()](#timestamp) 38 | - [@Timezone()](#timezone) 39 | - [@UserAgent()](#useragent) 40 | - [@Value()](#value) 41 | - [Validators](#validators) 42 | 43 | ## Usage 🚀 44 | 45 | ### Param Decorators 46 | 47 | #### @EnumQuery() 48 | 49 | Use the `EnumQuery` decorator when you expect a single enum value as a query parameter. This will validate the query parameter against a predefined enum and generate Swagger documentation for it. 50 | 51 | Parameters: 52 | 53 | - `options`: 54 | - `key`: The name of the query parameter (required). 55 | - `enum`: The enum to validate against (required). 56 | - `separator`: The delimiter for multiple values (optional, default: `,`). 57 | - `description`: A description for Swagger documentation (optional). 58 | - `nullable`: If true, the query parameter is allowed to be `null` (optional, default: `false`). 59 | - `singleValue`: Only used internally for `EnumQuery` to indicate a single value (you don't need to set this manually). 60 | 61 | Example for usage: 62 | 63 | ```typescript 64 | 65 | import { Controller, Get } from '@nestjs/common'; 66 | import { EnumQuery } from '@hodfords/nestjs-base-decorator'; 67 | 68 | enum StatusEnum { 69 | ACTIVE = 'active', 70 | INACTIVE = 'inactive', 71 | } 72 | 73 | @Controller() 74 | export class ItemController { 75 | @Get('items') 76 | getItemByStatus( 77 | @EnumQuery({ 78 | key: 'status', 79 | enum: StatusEnum, 80 | description: 'Status of the item', 81 | nullable: false, 82 | }) 83 | status: StatusEnum, 84 | ) { 85 | return status; 86 | } 87 | } 88 | 89 | /* 90 | Request: GET /items?status=active 91 | Response: active 92 | */ 93 | ``` 94 | 95 | #### @EnumsQuery() 96 | 97 | Use the `EnumsQuery` decorator when you expect multiple enum values as query parameters, separated by a custom delimiter (default is `,`). This will validate the query parameters against a predefined enum and generate Swagger documentation for them. 98 | 99 | Parameters: 100 | 101 | - `options`: 102 | 103 | - `key`: The name of the query parameter (required). 104 | - `enum`: The enum to validate against (required). 105 | - `separator`: The delimiter for multiple values (optional, default: `,`). 106 | - `description`: A description for Swagger documentation (optional). 107 | - `nullable`: If true, the query parameter is allowed to be `null` (optional, default: `false`). 108 | - `singleValue`: Only used internally for `EnumQuery` to indicate a single value (you don't need to set this manually). 109 | 110 | Example for usage: 111 | 112 | ```typescript 113 | import { Controller, Get } from '@nestjs/common'; 114 | import { EnumsQuery } from '@hodfords/nestjs-base-decorator'; 115 | 116 | enum CategoryEnum { 117 | ELECTRONICS = 'electronics', 118 | FASHION = 'fashion', 119 | BOOKS = 'books', 120 | } 121 | 122 | @Controller() 123 | export class ProductController { 124 | @Get('products') 125 | getProductsByCategories( 126 | @EnumsQuery({ 127 | key: 'categories', 128 | enum: CategoryEnum, 129 | description: 'Categories of the product', 130 | separator: ',', 131 | nullable: false, 132 | }) 133 | categories: CategoryEnum[], 134 | ) { 135 | return categories; 136 | } 137 | } 138 | 139 | /* 140 | Request: GET /products?categories=books,fashion 141 | Response: ["books", "fashion"] 142 | */ 143 | ``` 144 | 145 | #### @Id() 146 | 147 | Use the `Id` decorator to validate a single UUID, either from the route parameters or query parameters. If the provided value is not a valid UUID, it will throw a `UuidException` from the `@hodfords/nestjs-exception` package. 148 | 149 | Parameters: 150 | 151 | - `options` (`string` | `ParamOptions`): 152 | 153 | - `key`: The name of the parameter in the request (required). 154 | - `nullable`: If `true`, the parameter is allowed to be null (optional, default: `false`). 155 | 156 | Example for usage: 157 | 158 | ```typescript 159 | import { Controller, Get } from '@nestjs/common'; 160 | import { Id } from '@hodfords/nestjs-base-decorator'; 161 | 162 | @Controller('users') 163 | export class UserController { 164 | @Get(':id') 165 | getUserById(@Id('id') id: string) { 166 | return id; 167 | } 168 | } 169 | 170 | /* 171 | Request: GET users/8be26127-c0ed-4cad-bd45-7f40dcf53e89 172 | Response: 8be26127-c0ed-4cad-bd45-7f40dcf53e89 173 | */ 174 | ``` 175 | 176 | #### @Ids() 177 | 178 | Use the `Ids` decorator when you need to validate multiple UUIDs passed as a comma-separated list in the `query` parameters. If any value in the list is not a valid UUID, it throws a `UuidException` from the `@hodfords/nestjs-exception` package. 179 | 180 | Parameters: 181 | 182 | - `options` (`string` | `ParamOptions`): 183 | 184 | - `key`: The name of the parameter in the request (required). 185 | - `nullable`: If `true`, the parameter is allowed to be null (optional, default: `false`). 186 | 187 | Example for usage: 188 | 189 | ```typescript 190 | import { Controller, Get } from '@nestjs/common'; 191 | import { Ids } from '@hodfords/nestjs-base-decorator'; 192 | 193 | @Controller('users') 194 | export class UserController { 195 | @Get() 196 | getUsersByIds(@Ids('ids') ids: string[]) { 197 | return ids; 198 | } 199 | } 200 | 201 | /* 202 | Request: GET users?ids=8be26127-c0ed-4cad-bd45-7f40dcf53e89,1b3a0d50-2695-49e7-9498-c4cb1cada6e9 203 | Response: ["8be26127-c0ed-4cad-bd45-7f40dcf53e89","1b3a0d50-2695-49e7-9498-c4cb1cada6e9"] 204 | */ 205 | ``` 206 | 207 | #### @Int() 208 | 209 | The `Int` decorator is used to validate a single integer, either from route parameters or query parameters. 210 | 211 | Parameters: `key` (default: `'id'`) 212 | 213 | Example for usage: 214 | 215 | ```typescript 216 | import { Controller, Get } from '@nestjs/common'; 217 | import { Int } from '@hodfords/nestjs-base-decorator'; 218 | 219 | @Controller('users') 220 | export class UserController { 221 | @Get(':id') 222 | getUserById(@Int('id') id: number) { 223 | return id; 224 | } 225 | } 226 | 227 | /* 228 | Request: GET /users/123 229 | Response: 123 230 | */ 231 | ``` 232 | 233 | #### @Ints() 234 | 235 | The `Ints` decorator is used to validate multiple integers passed as a comma-separated list in query parameters. 236 | 237 | Parameters: `key` (default: `'ids'`) 238 | 239 | Example for usage: 240 | 241 | ```typescript 242 | import { Controller, Get } from '@nestjs/common'; 243 | import { Ints } from '@hodfords/nestjs-base-decorator'; 244 | 245 | @Controller('users') 246 | export class UserController { 247 | @Get() 248 | getUsersByIds(@Ints('ids') ids: number[]) { 249 | return ids; 250 | } 251 | } 252 | 253 | /* 254 | Request: GET /users?ids=123,456 255 | Response: [123,456] 256 | */ 257 | ``` 258 | 259 | #### @Pagination() 260 | 261 | The `Pagination` decorator is used to handle pagination logic by extracting the `page` and `perPage` parameters from the query string of an incoming request. The decorator also includes automatic Swagger documentation using `nestjs/swagger`. 262 | 263 | Parameters: 264 | 265 | - `paginationParams`: 266 | - `page`: The current page number (optional, default: 1). 267 | - `perPage`: The number of items per page (optional, default: 10, max: 1000). 268 | 269 | Example for usage: 270 | 271 | ```typescript 272 | import { Controller, Get } from '@nestjs/common'; 273 | import { Pagination, PaginationParams } from '@hodfords/nestjs-base-decorator'; 274 | 275 | @Controller('users') 276 | export class UserController { 277 | @Get() 278 | getUsers( 279 | @Pagination() pagination: PaginationParams 280 | ) { 281 | return `Page: ${pagination.page}, Per Page: ${pagination.perPage}`; 282 | } 283 | } 284 | 285 | /* 286 | Request: GET /users?page=1&perPage=10 287 | Response: Page: 1, Per Page: 10 288 | */ 289 | ``` 290 | 291 | #### @QueryBoolean() 292 | 293 | The `QueryBoolean` decorator allows you to extract and validate boolean values from the query parameters in a NestJS route. It checks if the query parameter is `'true'` and returns `true`, otherwise it returns `false`. 294 | 295 | Parameters: `key`, `options` (`ApiQueryOptions`) 296 | 297 | Example for usage: 298 | 299 | ```typescript 300 | import { Controller, Get } from '@nestjs/common'; 301 | import { QueryBoolean } from '@hodfords/nestjs-base-decorator'; 302 | 303 | @Controller('users') 304 | export class UsersController { 305 | @Get() 306 | getUsers(@QueryBoolean('isActive') isActive: boolean) { 307 | return isActive; 308 | } 309 | } 310 | 311 | /* 312 | Request: GET /users?isActive=true 313 | Response: true 314 | 315 | Request: GET /users?isActive=123 316 | Response: false 317 | */ 318 | ``` 319 | 320 | #### @QueryStrings() 321 | 322 | The `QueryStrings` decorator extracts query parameters from HTTP requests, parses them as comma-separated strings, and ensures uniqueness by eliminating duplicates. Additionally, it integrates with `@nestjs/swagger` to automatically document the query parameters in the Swagger UI. 323 | 324 | Parameters: `key`, `options` (`ApiQueryOptions`) 325 | 326 | Example for usage: 327 | 328 | ```typescript 329 | import { Controller, Get } from '@nestjs/common'; 330 | import { QueryStrings } from '@hodfords/nestjs-base-decorator'; 331 | 332 | @Controller() 333 | export class AppController { 334 | constructor() {} 335 | 336 | @Get() 337 | findTags(@QueryStrings('tags') tags: string[]): string[] { 338 | return tags; 339 | } 340 | } 341 | 342 | /* 343 | Request: GET ?tags=name,age 344 | Response: ["name, "age"] 345 | */ 346 | ``` 347 | 348 | #### @RealIp() 349 | 350 | The `RealIp` decorator is a custom NestJS decorator that retrieves the client's real IP address from an incoming HTTP request. It leverages the `@supercharge/request-ip` library to accurately identify the IP address, even if the client is behind a proxy or using a load balancer. 351 | 352 | Example for usage: 353 | 354 | ```typescript 355 | import { Controller, Get } from '@nestjs/common'; 356 | import { RealIp } from '@hodfords/nestjs-base-decorator'; 357 | 358 | @Controller('users') 359 | export class UserController { 360 | 361 | @Get('ip') 362 | getClientIp(@RealIp() ip: string): string { 363 | return `Client IP: ${ip}`; 364 | } 365 | } 366 | 367 | /* 368 | Request: GET /ip 369 | Response: "Client IP": "203.0.113.195" 370 | */ 371 | ``` 372 | 373 | #### @RequireToUploadFile() 374 | 375 | The `RequireToUploadFile` decorator is a custom NestJS decorator that simplifies file upload handling for single and multiple file uploads. It leverages NestJS `Interceptors` and custom interceptors to validate file uploads. The decorator allows developers to specify file upload configurations like file size limits, allowed MIME types, and custom storage engines. 376 | 377 | Parameters: 378 | 379 | - `fieldName`: Specifies the form field name used to upload the file(s). 380 | - `options`: An object that configures the file upload behavior. It includes: 381 | - `fileSize`: Maximum file size (in bytes) for the upload. Default is set to `10 * 1024 * 1024`. 382 | - `allowedMimeTypes`: Array of allowed MIME types for the uploaded files. Default is `['image/png', 383 | 'image/jpeg', 384 | 'image/jpg', 385 | 'image/svg+xml', 386 | 'image/bmp', 387 | 'image/heic', 388 | 'image/heif', 389 | 'application/pdf', 390 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 391 | 'application/vnd.ms-excel']`. 392 | - `maxCount`: Maximum number of files that can be uploaded in a single request. 393 | - `storageEngine`: Custom storage engine for handling uploaded files (e.g., disk storage or cloud storage). 394 | 395 | Example for usage: 396 | 397 | ```typescript 398 | import { Controller, Post, UploadedFiles } from '@nestjs/common'; 399 | import { RequireToUploadFile } from '@hodfords/nestjs-base-decorator'; 400 | 401 | @Controller('files') 402 | export class FileController { 403 | @Post('upload') 404 | @RequireToUploadFile({ fieldName: 'file' }) 405 | uploadFile(@UploadedFiles() file: Express.Multer.File[]) { 406 | return file; 407 | } 408 | } 409 | ``` 410 | 411 | If you want only upload with one file and present it as a object, you can follow below: 412 | 413 | ```typescript 414 | import { Controller, Post, UploadedFile } from '@nestjs/common'; 415 | import { RequireToUploadFile } from '@hodfords/nestjs-base-decorator'; 416 | 417 | @Controller('files') 418 | export class FileController { 419 | @Post('upload') 420 | @RequireToUploadFile({ fieldName: 'file', options: { maxCount: 1 } }) 421 | uploadFile(@UploadedFile() file: Express.Multer.File) { 422 | return file; 423 | } 424 | } 425 | ``` 426 | 427 | #### @RequireToUploadFiles() 428 | 429 | The `RequireToUploadFiles` decorator simplifies the process of uploading multiple files with different field names in NestJS. It uses the `FileFieldsInterceptor` from `@nestjs/platform-express` to manage multiple file uploads from distinct form fields and allows customization through file upload options. The decorator integrates with custom interceptors for additional validation and handling of the uploaded files. 430 | 431 | Parameters: 432 | 433 | - `fieldNames`: 434 | - `name`: The name of the form field. 435 | - `maxCount`: (Optional) Maximum number of files allowed for this field. If not provided, defaults to the global `maxCount` value. 436 | - `options`: 437 | - `fileSize`: Maximum file size (in bytes). 438 | - `allowedMimeTypes`: Array of allowed MIME types for uploaded files. 439 | - `maxCount`: Default maximum number of files allowed per field. 440 | - `storageEngine`: Custom storage engine to handle file uploads. 441 | 442 | Example for usage: 443 | 444 | ```typescript 445 | import { Controller, Post, UploadedFile } from '@nestjs/common'; 446 | import { RequireToUploadFile } from '@hodfords/nestjs-base-decorator'; 447 | 448 | @Controller('files') 449 | export class FileController { 450 | @Post('upload') 451 | @RequireToUploadFiles({ 452 | fieldNames: [ 453 | { name: 'profile', maxCount: 1 }, 454 | { name: 'documents', maxCount: 5 } 455 | ] 456 | }) 457 | uploadUserFiles(@UploadedFiles() files: { profile: Express.Multer.File[]; documents: Express.Multer.File[] }) { 458 | return files; 459 | } 460 | } 461 | ``` 462 | 463 | #### @Sort() 464 | 465 | The `Sort` decorator simplifies handling sorting parameters in NestJS controllers. It defines two query parameters, `sortField` and `sortDirection`, allowing API clients to specify how to sort data in their requests. The decorator integrates with Swagger for automatic API documentation and validates the sorting fields and directions against predefined sets of allowed values. 466 | 467 | Parameters: 468 | 469 | - `sortParams`: 470 | - `allowedFields`: An array of allowed fields for sorting (e.g., `['name', 'createdAt']`). 471 | - `default`: An object with default sorting parameters: 472 | - `sortField`: The default field to sort by. 473 | - `sortDirection`: The default sorting direction (`ASC` or `DESC`). 474 | 475 | Example for usage: 476 | 477 | ```typescript 478 | import { Controller, Get, Query } from '@nestjs/common'; 479 | import { Sort, SortParams } from '@hodfords/nestjs-base-decorator'; 480 | 481 | @Controller('users') 482 | export class UserController { 483 | 484 | @Get() 485 | getUsers(@Sort({ allowedFields: ['name', 'createdAt'], default: { sortField: 'createdAt', sortDirection: 'DESC' } }) query: SortParams) { 486 | const { sortField, sortDirection } = query; 487 | return `Sorted by ${sortField} in ${sortDirection} order`; 488 | } 489 | } 490 | 491 | /* 492 | Request: GET ?sortField=createdAt&sortDirection=DESC 493 | Response: Sorted by createdAt in DESC order 494 | */ 495 | ``` 496 | 497 | #### @Sorts() 498 | 499 | The `Sorts` decorator provides an elegant solution for handling multiple sorting fields in NestJS controllers. It allows clients to specify multiple fields and their respective sort directions. The decorator automatically generates Swagger documentation for these parameters and ensures proper validation of both fields and directions. 500 | 501 | Parameters: 502 | 503 | - `sortParams`: 504 | - `allowedFields`: An array of allowed fields for sorting (e.g., `['name', 'createdAt']`). 505 | - `default`: An object with default sorting parameters: 506 | - `sortField`: The default field to sort by. 507 | - `sortDirection`: The default sorting direction (`ASC` or `DESC`). 508 | 509 | Example for usage: 510 | 511 | ```typescript 512 | import { Controller, Get, Query } from '@nestjs/common'; 513 | import { Sorts, SortMultipleParams } from '@hodfords/nestjs-base-decorator'; 514 | 515 | @Controller('users') 516 | export class UserController { 517 | 518 | @Get() 519 | getUsers( 520 | @Sorts({ allowedFields: ['name', 'createdAt'], default: { sortField: 'createdAt', sortDirection: 'DESC' } }) 521 | query: SortMultipleParams[] 522 | ) { 523 | return query; 524 | } 525 | } 526 | 527 | /* 528 | Request: GET ?sortFields=createdAt,name:DESC 529 | Response: [ 530 | { 531 | "field": "createdAt", 532 | "direction": "ASC" 533 | }, 534 | { 535 | "field": "name", 536 | "direction": "DESC" 537 | } 538 | ] 539 | */ 540 | ``` 541 | 542 | #### @Timestamp() 543 | 544 | The `Timestamp` decorator is used to extract and validate a timestamp from the request parameters or query in a NestJS controller. It ensures the timestamp is valid and handles optional or nullable parameters. 545 | 546 | Parameters: 547 | 548 | - `options`: 549 | - `key`: The name of the parameter to extract (required). 550 | - `nullable`: Indicates if the timestamp can be nullable (optional, default: `false`). 551 | 552 | Example for usage: 553 | 554 | ```typescript 555 | import { Controller, Get, Query } from '@nestjs/common'; 556 | import { Timestamp } from '@hodfords/nestjs-base-decorator'; 557 | 558 | @Controller('events') 559 | export class EventController { 560 | 561 | @Get() 562 | getEvents(@Timestamp('timestamp') timestamp: number) { 563 | return timestamp; 564 | } 565 | } 566 | 567 | /* 568 | Request: GET /events?timestamp=1581739337 569 | Response: 1581739337 570 | */ 571 | ``` 572 | 573 | #### @Timezone() 574 | 575 | The `Timezone` decorator is designed to validate and extract a timezone string from request parameters or query in a NestJS controller. It leverages the `dayjs` library along with the `timezone` and `utc` plugins to ensure that the provided timezone is valid. 576 | 577 | Parameters: 578 | 579 | - `options`: 580 | - `key`: The name of the parameter to extract (required). 581 | - `nullable`: Indicates if the timestamp can be nullable (optional, default: `false`). 582 | 583 | Example for usage: 584 | 585 | ```typescript 586 | import { Controller, Get, Query } from '@nestjs/common'; 587 | import { Timezone } from '@hodfords/nestjs-base-decorator'; 588 | 589 | @Controller('events') 590 | export class EventController { 591 | 592 | @Get() 593 | getEvents(@Timezone('timezone') timezone: string) { 594 | return timezone; 595 | } 596 | } 597 | 598 | /* 599 | Request: GET ?timezone=America/New_York 600 | Response: America/New_York 601 | */ 602 | ``` 603 | 604 | #### @UserAgent() 605 | 606 | The `UserAgent` decorator is a simple utility that extracts the `User-Agent` header from incoming requests in a NestJS application. This header typically contains information about the client's browser, operating system, and device. 607 | 608 | Example for usage: 609 | 610 | ```typescript 611 | import { Controller, Get, Query } from '@nestjs/common'; 612 | import { UserAgent } from '@hodfords/nestjs-base-decorator'; 613 | 614 | @Controller('users') 615 | export class UserController { 616 | 617 | @Get('user-agent') 618 | getUserAgent(@UserAgent() userAgent: string) { 619 | return userAgent; 620 | } 621 | } 622 | ``` 623 | 624 | #### @Value() 625 | 626 | The `Value` decorator is used to extract the `value` property from the payload of a microservice request in NestJS. It is a wrapper around the `Payload` decorator provided by `@nestjs/microservices`, allowing you to directly access the `value` property in microservice message handlers. 627 | 628 | Example for usage: 629 | 630 | ```typescript 631 | import { Controller } from '@nestjs/common'; 632 | import { EventPattern } from '@nestjs/microservices'; 633 | import { Value } from '@hodfords/nestjs-base-decorator'; 634 | 635 | @Controller() 636 | export class MathController { 637 | 638 | @EventPattern('math.add') 639 | handleAddition(@Value() value: number) { 640 | return `Value received: ${value}`; 641 | } 642 | } 643 | 644 | ``` 645 | 646 | ### Validators 647 | 648 | #### @ExistIds() 649 | 650 | The `@ExistIds()` decorator is used to validate whether all provided IDs exist in a specified database table column. It uses `TypeORM` to query the table and check if the given values match the records in the database. 651 | 652 | Parameters: 653 | 654 | - `table`: The `Entity` class (which extends `BaseEntity`) represents the database table where the validation will be performed. 655 | - `allowEmpty`: A boolean to allow empty arrays as valid input (optional, default: `false`). 656 | - `validationOptions`: `ValidationOptions` from `class-validator` to configure custom messages and options. 657 | 658 | Example for usage: 659 | 660 | ```typescript 661 | import { ExistIds } from '@hodfords/nestjs-base-decorator'; 662 | import { Entity } from 'typeorm'; 663 | import { BaseEntity } from '@hodfords/typeorm-helper'; 664 | 665 | @Entity() 666 | export class UserEntity extends BaseEntity { 667 | @PrimaryGeneratedColumn('uuid') 668 | id: string; 669 | } 670 | 671 | class UserRequestDto { 672 | @ExistIds(UserEntity) 673 | userIds: string[]; 674 | } 675 | ``` 676 | 677 | #### @Exists() 678 | 679 | The `@Exists()` decorator is used to validate if a specific value exists in a column of a database table. It supports case-insensitive searches and custom query conditions to allow more flexible validations. 680 | 681 | Parameters: 682 | 683 | - `table`: The `Entity` class (which extends `BaseEntity`) represents the database table where the validation will be performed. 684 | - `column`: The column name in the database to check the existence of the value. 685 | - `caseInsensitive`: A boolean to enable case-insensitive comparison (option, default: `false`). 686 | - `customs`: An array of custom conditions that apply additional filters to the query (optional). 687 | - `validationOptions`: `ValidationOptions` from `class-validator` to configure custom messages and options (optional). 688 | 689 | ```typescript 690 | import { Exists } from '@hodfords/nestjs-base-decorator'; 691 | import { Entity } from 'typeorm'; 692 | import { IsEmail } from 'class-validator'; 693 | import { BaseEntity } from '@hodfords/typeorm-helper'; 694 | 695 | @Entity() 696 | export class UserEntity extends BaseEntity { 697 | @Column() 698 | email: string; 699 | } 700 | 701 | class UserRequestDto { 702 | @Exists(UserEntity, 'email') 703 | @IsEmail() 704 | email: string; 705 | } 706 | ``` 707 | 708 | ## License 709 | 710 | This project is licensed under the MIT License 711 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": [ 5 | "hodfords", 6 | "ILIKE", 7 | "Ints", 8 | "nestjs", 9 | "npmjs", 10 | "officedocument", 11 | "openxmlformats", 12 | "postbuild", 13 | "spreadsheetml", 14 | "typeorm", 15 | "uuidv4" 16 | ], 17 | "flagWords": [ 18 | "hte" 19 | ], 20 | "ignorePaths": [ 21 | "node_modules", 22 | "test", 23 | "*.spec.ts", 24 | "cspell.json" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@hodfords/nestjs-eslint-config'); 2 | -------------------------------------------------------------------------------- /lib/constants/upload-file.constant.ts: -------------------------------------------------------------------------------- 1 | export const FILES_UPLOAD_MAXIMUM = 5; 2 | 3 | export const FILE_UPLOAD_MAX_SIZE = 10 * 1024 * 1024; 4 | 5 | export const FILE_UPLOAD_DESTINATION = './tmp/upload-files'; 6 | 7 | export const ALLOWED_FILE_UPLOAD_MIME_TYPES = [ 8 | 'image/png', 9 | 'image/jpeg', 10 | 'image/jpg', 11 | 'image/svg+xml', 12 | 'image/bmp', 13 | 'image/heic', 14 | 'image/heif', 15 | 'application/pdf', 16 | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 17 | 'application/vnd.ms-excel' 18 | ]; 19 | -------------------------------------------------------------------------------- /lib/decorators/enums.decorators.ts: -------------------------------------------------------------------------------- 1 | import { ValidateFieldException } from '@hodfords/nestjs-exception'; 2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 3 | import { ApiQuery } from '@nestjs/swagger'; 4 | import { SwaggerEnumType } from '@nestjs/swagger/dist/types/swagger-enum.type'; 5 | import { getParamOptions, ParamOptions } from '../helpers/get-params.helper'; 6 | 7 | type EnumsParamOptions = ParamOptions & { 8 | enum: SwaggerEnumType; 9 | separator?: string; 10 | description?: string; 11 | singleValue?: boolean; 12 | example?: string; 13 | }; 14 | 15 | const enumsDecorator = createParamDecorator((options: EnumsParamOptions, ctx: ExecutionContext) => { 16 | const paramOptions = getParamOptions(options, 'id'); 17 | const request = ctx.switchToHttp().getRequest(); 18 | const { separator = ',' } = options; 19 | 20 | const values: string[] = (request.params[paramOptions.key] || request.query[paramOptions.key])?.split(separator); 21 | if (!values && paramOptions.nullable) { 22 | return values; 23 | } 24 | 25 | const enumValues = Object.values(options.enum).map((item) => String(item)); 26 | if (options.singleValue) { 27 | if ((!values?.at(0) && !paramOptions.nullable) || (values?.at(0) && !enumValues.includes(values[0]))) { 28 | throw new ValidateFieldException(options.key, 'invalid_enum_value', 'invalidEnum'); 29 | } 30 | 31 | return values[0]; 32 | } 33 | 34 | const hasMismatched = values.some((item) => !enumValues.includes(item)); 35 | if (hasMismatched) { 36 | throw new ValidateFieldException(options.key, 'invalid_enum_value', 'invalidEnum'); 37 | } 38 | 39 | return values; 40 | }); 41 | 42 | export function EnumQuery(options: EnumsParamOptions) { 43 | return (target: any, key: string, descriptor: any) => { 44 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); 45 | if (propertyDescriptor) { 46 | ApiQuery({ 47 | description: options.description, 48 | name: options.key, 49 | enum: options.enum, 50 | required: !options.nullable 51 | })(target, key, propertyDescriptor); 52 | } 53 | return enumsDecorator({ ...options, singleValue: true })(target, key, descriptor); 54 | }; 55 | } 56 | 57 | export function EnumsQuery(options: EnumsParamOptions) { 58 | return (target: any, key: string, descriptor: any) => { 59 | const enumKeys = Object.keys(options.enum).filter((item) => isNaN(+item)); 60 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); 61 | if (propertyDescriptor) { 62 | ApiQuery({ 63 | description: options.description || `Available enums: ${enumKeys}`, 64 | example: options.example || 'value1,value2,...', 65 | name: options.key, 66 | required: !options.nullable 67 | })(target, key, propertyDescriptor); 68 | } 69 | return enumsDecorator(options)(target, key, descriptor); 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /lib/decorators/id.decorator.ts: -------------------------------------------------------------------------------- 1 | import { UuidException } from '@hodfords/nestjs-exception'; 2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 3 | import { validate } from 'uuid'; 4 | import { getParamOptions, ParamOptions } from '../helpers/get-params.helper'; 5 | 6 | export const Id = createParamDecorator((options: ParamOptions | string, ctx: ExecutionContext) => { 7 | const paramOptions = getParamOptions(options, 'id'); 8 | const request = ctx.switchToHttp().getRequest(); 9 | const id = request.params[paramOptions.key] || request.query[paramOptions.key]; 10 | if (!id && paramOptions.nullable) { 11 | return id; 12 | } 13 | if (!validate(id)) { 14 | throw new UuidException(paramOptions.key); 15 | } 16 | return id; 17 | }); 18 | 19 | export const Ids = createParamDecorator((options: ParamOptions | string, ctx: ExecutionContext) => { 20 | const paramOptions = getParamOptions(options, 'ids'); 21 | const request = ctx.switchToHttp().getRequest(); 22 | const ids = request.query[paramOptions.key]; 23 | if (!ids) { 24 | return []; 25 | } 26 | return ids 27 | .split(',') 28 | .map((id: string) => { 29 | if (!id && paramOptions.nullable) { 30 | return id; 31 | } 32 | if (!validate(id)) { 33 | throw new UuidException(paramOptions.key); 34 | } 35 | return id; 36 | }) 37 | .filter((id: string) => id); 38 | }); 39 | -------------------------------------------------------------------------------- /lib/decorators/number.decorator.ts: -------------------------------------------------------------------------------- 1 | import { UuidException } from '@hodfords/nestjs-exception'; 2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 3 | 4 | export const Int = createParamDecorator((key: string, ctx: ExecutionContext) => { 5 | if (!key) { 6 | key = 'id'; 7 | } 8 | const request = ctx.switchToHttp().getRequest(); 9 | const id = request.params[key] || request.query[key]; 10 | 11 | const value = parseInt(id); 12 | if (isNaN(value)) { 13 | throw new UuidException(key); 14 | } 15 | return value; 16 | }); 17 | 18 | export const Ints = createParamDecorator((key: string, ctx: ExecutionContext) => { 19 | const request = ctx.switchToHttp().getRequest(); 20 | const ids = request.query[key || 'ids']; 21 | 22 | if (!ids) { 23 | return []; 24 | } 25 | 26 | return ids.split(',').map((id: string) => { 27 | const value = parseInt(id); 28 | if (isNaN(value)) { 29 | throw new UuidException(key); 30 | } 31 | return value; 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /lib/decorators/pagination.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { ApiQuery } from '@nestjs/swagger'; 3 | 4 | export const Pagination = createParamDecorator( 5 | (paginationParams: PaginationInputParams | undefined, ctx: ExecutionContext) => { 6 | if (!paginationParams) { 7 | paginationParams = { 8 | page: 'page', 9 | perPage: 'perPage' 10 | }; 11 | } 12 | if (!paginationParams.page) { 13 | paginationParams.page = 'page'; 14 | } 15 | if (!paginationParams.perPage) { 16 | paginationParams.perPage = 'perPage'; 17 | } 18 | 19 | const request = ctx.switchToHttp().getRequest(); 20 | const defaultPerPage = 10; 21 | const maxPerPage = 1000; 22 | 23 | return { 24 | page: Math.max(request.query[paginationParams.page], 1) || 1, 25 | perPage: Math.min(request.query[paginationParams.perPage] || defaultPerPage, maxPerPage) || defaultPerPage 26 | }; 27 | }, 28 | [ 29 | ((target: any, key: string | symbol) => { 30 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); 31 | if (propertyDescriptor) { 32 | ApiQuery({ 33 | name: 'page', 34 | schema: { default: 1, type: 'number', minimum: 1 }, 35 | required: false 36 | })(target, key, propertyDescriptor); 37 | ApiQuery({ 38 | name: 'perPage', 39 | schema: { default: 10, type: 'number', minimum: 1, maximum: 1000 }, 40 | required: false 41 | })(target, key, propertyDescriptor); 42 | } 43 | }) as ParameterDecorator 44 | ] 45 | ); 46 | 47 | export interface PaginationParams { 48 | page: number; 49 | perPage: number; 50 | } 51 | 52 | export interface PaginationInputParams { 53 | page?: string; 54 | perPage?: string; 55 | } 56 | -------------------------------------------------------------------------------- /lib/decorators/query-boolean.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { ApiQuery, ApiQueryOptions } from '@nestjs/swagger'; 3 | 4 | const queryBooleanDecorator = createParamDecorator((key: string, ctx: ExecutionContext) => { 5 | const request = ctx.switchToHttp().getRequest(); 6 | return request.query[key] === 'true'; 7 | }); 8 | 9 | export function QueryBoolean(key: string, options: ApiQueryOptions = {}) { 10 | return (target: any, property: string, descriptor: any) => { 11 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, property); 12 | if (propertyDescriptor) { 13 | ApiQuery({ 14 | name: key, 15 | required: false, 16 | type: Boolean, 17 | ...options 18 | })(target, property, propertyDescriptor); 19 | } 20 | return queryBooleanDecorator(key)(target, property, descriptor); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /lib/decorators/query-strings.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { ApiQuery, ApiQueryOptions } from '@nestjs/swagger'; 3 | 4 | const queryStringsDecorator = createParamDecorator((key: string, ctx: ExecutionContext): string[] => { 5 | const request = ctx.switchToHttp().getRequest(); 6 | const value = request.query[key]; 7 | if (value !== null && value !== undefined && typeof value === 'string') { 8 | return [...new Set(value.split(','))]; 9 | } 10 | return []; 11 | }); 12 | 13 | export function QueryStrings(key: string, options: ApiQueryOptions = {}) { 14 | return (target: any, property: string, descriptor: any) => { 15 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, property); 16 | if (propertyDescriptor) { 17 | ApiQuery({ 18 | name: key, 19 | required: false, 20 | type: String, 21 | example: `value1,value2,...`, 22 | ...options 23 | })(target, property, propertyDescriptor); 24 | } 25 | return queryStringsDecorator(key)(target, property, descriptor); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /lib/decorators/real-ip.decorators.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import * as requestIp from '@supercharge/request-ip'; 3 | 4 | export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext) => { 5 | const request = ctx.switchToHttp().getRequest(); 6 | return requestIp.getClientIp(request); 7 | }); 8 | -------------------------------------------------------------------------------- /lib/decorators/require-upload-file.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, UseInterceptors } from '@nestjs/common'; 2 | import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express'; 3 | import { getFileUploadConfigurations } from '../helpers/file.helper'; 4 | import { RequireToUploadFileInterceptor } from '../interceptors/require-upload-file-interceptor'; 5 | import { RequireToUploadFilesInterceptor } from '../interceptors/require-upload-files-interceptor'; 6 | import { FileUploadParamsType } from '../types/file-upload-params.type'; 7 | 8 | export function RequireToUploadFile({ fieldName, options }: FileUploadParamsType) { 9 | const { maxCount, multerOptions } = getFileUploadConfigurations(options || {}); 10 | const isArray = maxCount > 1; 11 | 12 | if (isArray) { 13 | return applyDecorators( 14 | UseInterceptors(FilesInterceptor(fieldName, maxCount, multerOptions)), 15 | UseInterceptors(RequireToUploadFilesInterceptor) 16 | ); 17 | } else { 18 | return applyDecorators( 19 | UseInterceptors(FileInterceptor(fieldName, multerOptions)), 20 | UseInterceptors(RequireToUploadFileInterceptor) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/decorators/require-upload-files.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, SetMetadata, UseInterceptors } from '@nestjs/common'; 2 | import { FileFieldsInterceptor } from '@nestjs/platform-express'; 3 | import { getFileUploadConfigurations } from '../helpers/file.helper'; 4 | import { RequireToUploadFilesInterceptor } from '../interceptors/require-upload-files-interceptor'; 5 | import { FilesUploadParamsType } from '../types/files-upload-params.type'; 6 | 7 | export function RequireToUploadFiles({ fieldNames, options }: FilesUploadParamsType) { 8 | const { maxCount: defaultMaxCount, multerOptions } = getFileUploadConfigurations(options || {}); 9 | const uploadFields = fieldNames.map(({ name, maxCount }) => { 10 | return { 11 | name, 12 | maxCount: maxCount && maxCount > 0 ? maxCount : defaultMaxCount 13 | }; 14 | }); 15 | 16 | const names = uploadFields.map(({ name }) => name); 17 | return applyDecorators( 18 | SetMetadata('fieldNames', names), 19 | UseInterceptors(FileFieldsInterceptor(uploadFields, multerOptions)), 20 | UseInterceptors(RequireToUploadFilesInterceptor) 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/decorators/sort.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { ApiQuery } from '@nestjs/swagger'; 3 | import { getAllowedFieldsEnums, validateDirection, validateField } from '../helpers/sort.helper'; 4 | import { SortDirection } from '../types/sort-direction.type'; 5 | import { SortParamsType } from '../types/sort-params.type'; 6 | 7 | export function Sort(sortParams: SortParamsType): any { 8 | return (target: any, key: string, descriptor: any) => { 9 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); 10 | 11 | if (propertyDescriptor) { 12 | ApiQuery({ 13 | name: 'sortField', 14 | enum: getAllowedFieldsEnums(sortParams), 15 | schema: { default: sortParams?.default?.sortField || 'createdAt', type: 'string' }, 16 | required: false 17 | })(target, key, propertyDescriptor); 18 | ApiQuery({ 19 | name: 'sortDirection', 20 | schema: { default: 'DESC', type: 'string' }, 21 | enum: ['ASC', 'DESC'], 22 | required: false 23 | })(target, key, propertyDescriptor); 24 | } 25 | return sortDecorator(sortParams)(target, key, descriptor); 26 | }; 27 | } 28 | 29 | const sortDecorator = createParamDecorator((sortParams: SortParamsType, ctx: ExecutionContext): SortParams => { 30 | if (!sortParams) { 31 | sortParams = { sortField: 'sortField', sortDirection: 'sortDirection' }; 32 | } 33 | if (!sortParams.sortField) { 34 | sortParams.sortField = 'sortField'; 35 | } 36 | if (!sortParams.sortDirection) { 37 | sortParams.sortDirection = 'sortDirection'; 38 | } 39 | 40 | const request = ctx.switchToHttp().getRequest(); 41 | const sortField = request.query[sortParams.sortField] || sortParams?.default?.sortField || 'createdAt'; 42 | const sortDirection = request.query[sortParams.sortDirection] || sortParams?.default?.sortDirection || 'DESC'; 43 | 44 | validateField(getAllowedFieldsEnums(sortParams), sortField); 45 | validateDirection(sortDirection); 46 | 47 | return { 48 | sortField, 49 | sortDirection 50 | }; 51 | }); 52 | 53 | export type SortParams = { 54 | sortField: string; 55 | sortDirection: SortDirection; 56 | }; 57 | -------------------------------------------------------------------------------- /lib/decorators/sorts.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { ApiQuery } from '@nestjs/swagger'; 3 | import { getAllowedFieldsEnums, validateDirection, validateField } from '../helpers/sort.helper'; 4 | import { SortDirection } from '../types/sort-direction.type'; 5 | import { SortsParamsType } from '../types/sorts-params.type'; 6 | 7 | export function Sorts(sortParams: SortsParamsType): any { 8 | return (target: any, key: string, descriptor: any) => { 9 | const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key); 10 | if (propertyDescriptor) { 11 | ApiQuery({ 12 | description: `Available values: ${getAllowedFieldsEnums(sortParams)}`, 13 | example: 'field1,field2:[ ASC(default) | DESC ],...', 14 | name: 'sortFields', 15 | schema: { 16 | default: 17 | `${sortParams?.default?.sortField}:${sortParams?.default?.sortDirection || 'DESC'}` || 18 | 'createdAt:DESC', 19 | type: 'string' 20 | }, 21 | required: false 22 | })(target, key, propertyDescriptor); 23 | } 24 | return sortDecorator(sortParams)(target, key, descriptor); 25 | }; 26 | } 27 | 28 | const sortDecorator = createParamDecorator( 29 | (sortParams: SortsParamsType, ctx: ExecutionContext): SortMultipleParams[] => { 30 | const request = ctx.switchToHttp().getRequest(); 31 | 32 | if (!sortParams) { 33 | sortParams = { sortFields: 'sortFields' }; 34 | } 35 | if (!sortParams.sortFields) { 36 | sortParams.sortFields = 'sortFields'; 37 | } 38 | 39 | const fields: string = request.query[sortParams.sortFields] ? request.query[sortParams.sortFields].trim() : ''; 40 | 41 | const sortFields = 42 | fields || `${sortParams?.default?.sortField}:${sortParams?.default?.sortDirection}` || 'createdAt:DESC'; 43 | return sortFields 44 | .split(',') 45 | .filter((sort) => sort) 46 | .map((sort: string) => { 47 | const sortArr: string[] = sort.split(':', 2); 48 | const sortOrder: SortMultipleParams = { 49 | field: sortArr[0].trim(), 50 | direction: ((sortArr[1] ? sortArr[1].toUpperCase() : undefined) as any) || 'ASC' 51 | }; 52 | validateField(getAllowedFieldsEnums(sortParams), sortOrder.field, true); 53 | validateDirection(sortOrder.direction); 54 | 55 | return sortOrder; 56 | }); 57 | } 58 | ); 59 | 60 | export type SortMultipleParams = { 61 | field: string; 62 | direction: SortDirection; 63 | }; 64 | -------------------------------------------------------------------------------- /lib/decorators/timestamp.decorator.ts: -------------------------------------------------------------------------------- 1 | import { ValidateFieldException } from '@hodfords/nestjs-exception'; 2 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 3 | import dayjs from 'dayjs'; 4 | import { getParamOptions, ParamOptions } from '../helpers/get-params.helper'; 5 | 6 | export const Timestamp = createParamDecorator((options: ParamOptions | string, ctx: ExecutionContext) => { 7 | const paramOptions = getParamOptions(options); 8 | const request = ctx.switchToHttp().getRequest(); 9 | const timestamp = request.params[paramOptions.key] || request.query[paramOptions.key]; 10 | 11 | if (!timestamp && paramOptions.nullable) { 12 | return timestamp; 13 | } 14 | 15 | if (!dayjs.unix(timestamp).isValid()) { 16 | throw new ValidateFieldException(paramOptions.key, 'invalid_timestamp', 'invalidTimestamp'); 17 | } 18 | 19 | return timestamp; 20 | }); 21 | -------------------------------------------------------------------------------- /lib/decorators/timezone.decorator.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import dayjs from 'dayjs'; 3 | import timezone from 'dayjs/plugin/timezone'; 4 | import utc from 'dayjs/plugin/utc'; 5 | import { getParamOptions, ParamOptions } from '../helpers/get-params.helper'; 6 | 7 | dayjs.extend(utc); 8 | dayjs.extend(timezone); 9 | 10 | export const Timezone = createParamDecorator((options: ParamOptions | string, ctx: ExecutionContext) => { 11 | const paramOptions = getParamOptions(options); 12 | const request = ctx.switchToHttp().getRequest(); 13 | const timezone = request.params[paramOptions.key] || request.query[paramOptions.key]; 14 | if (!timezone && paramOptions.nullable) { 15 | return timezone; 16 | } 17 | 18 | try { 19 | const dayjsLocal = dayjs(new Date()); 20 | if (dayjsLocal.tz(timezone)) { 21 | return timezone; 22 | } 23 | throw new BadRequestException({ translate: 'error.invalid_timezone' }); 24 | } catch { 25 | throw new BadRequestException({ translate: 'error.invalid_timezone' }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /lib/decorators/user-agent.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | 3 | export const UserAgent = createParamDecorator((options: unknown, ctx: ExecutionContext) => { 4 | const request = ctx.switchToHttp().getRequest(); 5 | 6 | return request.headers['user-agent']; 7 | }); 8 | -------------------------------------------------------------------------------- /lib/decorators/value.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Payload } from '@nestjs/microservices'; 2 | 3 | export function Value(): ParameterDecorator { 4 | return (target: any, key: string | symbol | undefined, descriptor: any): void => { 5 | return Payload('value')(target, key, descriptor); 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /lib/filters/file-upload.filter.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException } from '@nestjs/common'; 2 | import { Request } from 'express'; 3 | 4 | function isValidFileType(file: Express.Multer.File, allowedMimeTypes: string[]): boolean { 5 | const splitFileName = file.originalname.split('.'); 6 | 7 | return splitFileName.length >= 2 && allowedMimeTypes.includes(file.mimetype); 8 | } 9 | 10 | function isValidUploadMultipleFiles(files: Array