From 8286302a8c7036ebbc593e442a865d195f285093 Mon Sep 17 00:00:00 2001 From: ali aslani Date: Wed, 28 Dec 2022 22:46:20 +0330 Subject: [PATCH] Feature: Excel Export --- .env.example | 7 +++++- README.md | 2 +- package.json | 4 ++- src/app.module.ts | 2 +- src/common/common.service.ts | 19 ++++++++++----- src/excel/excel.service.spec.ts | 18 ++++++++++++++ src/excel/excel.service.ts | 42 ++++++++++++++++++++++++++++++++ src/user/Dtos/user.search.dto.ts | 3 +++ src/user/user.controller.ts | 17 ++++++++++--- 9 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 src/excel/excel.service.spec.ts create mode 100644 src/excel/excel.service.ts diff --git a/.env.example b/.env.example index b89db69..4d4220e 100644 --- a/.env.example +++ b/.env.example @@ -12,4 +12,9 @@ SWAGGER=true REDIS_HOST=localhost REDIS_PORT=6379 -REDIS_TTL=300 \ No newline at end of file +REDIS_TTL=300 + + +TOKEN_SECRET=secretKey + +LANGUAGE=fa \ No newline at end of file diff --git a/README.md b/README.md index 509aae7..39bbdb9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ 6. add filters *DONE 7. add csv export 8. add dwonload avatar -9. add user status +9. add user status *DONE 10. validate permission (no match, wrong structure) *DONE 11. update role's permission 12. name the routes diff --git a/package.json b/package.json index 491286b..44e7a81 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,8 @@ "cache-manager-ioredis": "^2.1.0", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", + "date-fns-jalali": "^2.29.3-0", + "exceljs": "^4.3.0", "nest-commander": "^3.3.0", "passport-jwt": "^4.0.0", "pg": "^8.8.0", @@ -85,4 +87,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} diff --git a/src/app.module.ts b/src/app.module.ts index edb3277..39586de 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -35,7 +35,7 @@ import { ConfigModule } from '@nestjs/config'; CommonModule, TypeOrmModule.forFeature([RequestLog]), RequestLogModule, - CacheModule + CacheModule, ], providers: [ { diff --git a/src/common/common.service.ts b/src/common/common.service.ts index 38558a2..8d3c2fb 100644 --- a/src/common/common.service.ts +++ b/src/common/common.service.ts @@ -1,21 +1,24 @@ import { Injectable } from '@nestjs/common'; +import { ExcelService } from 'src/excel/excel.service'; import { Repository } from 'typeorm'; @Injectable() -export abstract class CommonService { +export abstract class CommonService extends ExcelService { protected constructor ( - protected readonly repository: Repository - ) { } + protected readonly repository: Repository, + ) { + super() + } - async search(payload) { + async search(payload, columns?: { header: string, key: string, width: number }[]) { - const { size = 10, page = 1, all, ...params }: { size?: number, page?: number, all?: boolean } & { [key in string]: string } = payload + const { size = 10, page = 1, all, getExcel, ...params }: { size?: number, page?: number, all?: boolean, getExcel?: boolean } & { [key in string]: string } = payload let query = this.repository.createQueryBuilder("repository") - if (!all) { + if (!all || !getExcel) { query.skip((page - 1) * size) query.take(size) } @@ -33,6 +36,10 @@ export abstract class CommonService { const [data, total] = await query.getManyAndCount() + if (getExcel) { + const defaultColumns = Object.keys(data[0] || []).map(key => ({ header: key, key, width: 30 })) + return await this.exportExcel(params.sheetName || "deafult", data, columns || defaultColumns) + } return { data, diff --git a/src/excel/excel.service.spec.ts b/src/excel/excel.service.spec.ts new file mode 100644 index 0000000..013ef8d --- /dev/null +++ b/src/excel/excel.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ExcelService } from './excel.service'; + +describe('ExcelService', () => { + let service: ExcelService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ExcelService], + }).compile(); + + service = module.get(ExcelService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts new file mode 100644 index 0000000..d09d0a7 --- /dev/null +++ b/src/excel/excel.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { CommonEntity } from 'src/common/models/common.entity'; +import * as excel from 'exceljs'; +import { format } from 'date-fns-jalali'; + +@Injectable() +export class ExcelService { + + async exportExcel( + sheetName: string, + records: CommonEntity[] = [], + columns: object[] = [] + ): Promise { + + //creating workbook + let workbook = new excel.Workbook(); + let worksheet = workbook.addWorksheet(sheetName); + + // change time + if (process.env.LANGUAGE === "fa") { + + records.forEach((record: CommonEntity) => { + + if (record.updated_at) { + record.updated_at = format(record.updated_at as any, "yyyy-MM-dd"); + } + + + if (record.created_at) { + record.created_at = format(record.created_at as any, "yyyy-MM-dd"); + } + }); + + } + worksheet.columns = columns; + worksheet.addRows(records); + + const buffer = await workbook.xlsx.writeBuffer(); + + return buffer; + } +} diff --git a/src/user/Dtos/user.search.dto.ts b/src/user/Dtos/user.search.dto.ts index dfc8b3a..a5f87d1 100644 --- a/src/user/Dtos/user.search.dto.ts +++ b/src/user/Dtos/user.search.dto.ts @@ -15,6 +15,9 @@ export class UserSearchDto { @ApiProperty({ required: false }) all: boolean + @ApiProperty({ required: false }) + getExcel: boolean + @ApiProperty({ required: false }) roles: string diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 76ca793..0635d1e 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,4 +1,4 @@ -import { Body, ClassSerializerInterceptor, Controller, Get, Param, Post, Put, Query, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; +import { Body, ClassSerializerInterceptor, Controller, Get, Param, Post, Put, Query, Response, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiProperty, ApiTags } from '@nestjs/swagger'; import { AuthGuard } from 'src/auth/auth.guard'; @@ -16,8 +16,19 @@ export class UserController { constructor (private userService: UserService) { } @Get("search") - async search(@Query() payload: UserSearchDto) { - return await this.userService.search({ ...payload, roles: ":relation" }) + async search(@Query() payload: UserSearchDto, @Response() res) { + const data = await this.userService.search({ ...payload, roles: ":relation", sheetName: "users" }) + + if (payload.getExcel) { + res.header( + "Content-type", + "application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ) + .header("Content-Disposition", 'Content-Disposition: attachment; filename="users.xls"') + .send(data); + } + + return data } @Put("update/:id")