diff --git a/.dockerignore b/.dockerignore index 34a4261c0..279a8592a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ **/node_modules **/.next -**/.turbo **/dist **/.env* -!apps/web/.env.build \ No newline at end of file +!apps/web/.env.build +docker +volumes \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b5f9ae21c..000000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -jest.config.* -openapi.type.ts -api.type.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e35135249..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - root: true, -}; diff --git a/.github/workflows/docker-beta-image.yml b/.github/workflows/docker-beta-image.yml index f43b7022e..2efeabd67 100644 --- a/.github/workflows/docker-beta-image.yml +++ b/.github/workflows/docker-beta-image.yml @@ -34,7 +34,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./apps/api/Dockerfile + file: ./docker/api.dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.api-meta.outputs.tags }} labels: ${{ steps.api-meta.outputs.labels }} @@ -67,7 +67,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./apps/web/Dockerfile + file: ./docker/web.dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.web-meta.outputs.tags }} labels: ${{ steps.web-meta.outputs.labels }} diff --git a/.github/workflows/docker-dev-image.yml b/.github/workflows/docker-dev-image.yml index 47ef03d63..3fbd81d04 100644 --- a/.github/workflows/docker-dev-image.yml +++ b/.github/workflows/docker-dev-image.yml @@ -34,7 +34,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./apps/api/Dockerfile + file: ./docker/api.dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.api-meta.outputs.tags }} labels: ${{ steps.api-meta.outputs.labels }} @@ -67,7 +67,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./apps/web/Dockerfile + file: ./docker/web.dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.web-meta.outputs.tags }} labels: ${{ steps.web-meta.outputs.labels }} diff --git a/.github/workflows/docker-prod-image.yml b/.github/workflows/docker-prod-image.yml index 8e2711d1b..cb5baaeb6 100644 --- a/.github/workflows/docker-prod-image.yml +++ b/.github/workflows/docker-prod-image.yml @@ -36,7 +36,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./apps/api/Dockerfile + file: ./docker/api.dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.api-meta.outputs.tags }} labels: ${{ steps.api-meta.outputs.labels }} @@ -69,7 +69,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: ./apps/web/Dockerfile + file: ./docker/web.dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.web-meta.outputs.tags }} labels: ${{ steps.web-meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 502e32f42..8db8e97fb 100644 --- a/.gitignore +++ b/.gitignore @@ -30,15 +30,14 @@ yarn-error.log* .env.production.local # turbo -.turbo +!.turbo/.gitkeep +**/.turbo # .env .env* !.env.example !.env.build - -.vscode volumes dist \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index c0e11b911..3b9104d27 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,6 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" +yarn typecheck yarn format yarn lint diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 0f9ac9875..000000000 --- a/.prettierignore +++ /dev/null @@ -1,11 +0,0 @@ -migrations - -openapi.type.ts -api.type.ts - -dist -.next - -volumes - -node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 983f234af..000000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all", - "importOrder": ["", "^@/(.*)$", "^[./]", "^[../]"], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true, - "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"] -} diff --git a/.turbo/.gitkeep b/.turbo/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..a442d991c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "bradlc.vscode-tailwindcss", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "expo.vscode-expo-tools", + "yoavbls.pretty-ts-errors" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..ef83a6f07 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }], + "eslint.workingDirectories": [ + { "pattern": "apps/*/" }, + { "pattern": "packages/*/" }, + { "pattern": "tooling/*/" } + ], + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/README.md b/README.md index f9a729db3..43b2a9fbc 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,16 @@ npm run migration:run yarn dev ``` +6. Also, you can run the `dev` target of one of apps in root directory: + +```bash +# web +yarn turbo run dev --filter=web + +# api +yarn turbo run dev --filter=api +``` + You can always find more information in each app/library's respective README.md file. ### Setting Up ABC User Feedback Manually @@ -110,7 +120,7 @@ The client is based on React, React Hook Form, React Query, Tailwind css, MUI, a ### Build Docker Image -For your code build, you can buile docker image using docker-compose +For your code build, you can buile docker image using docker-compose. Please refer to [remote caching](https://turbo.build/repo/docs/core-concepts/remote-caching) and [deploying with docker](https://turbo.build/repo/docs/handbook/deploying-with-docker) using turborepo ``` docker-compose build diff --git a/apps/api/.eslintrc.js b/apps/api/.eslintrc.js index b42f4d403..5871e694d 100644 --- a/apps/api/.eslintrc.js +++ b/apps/api/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { root: true, - extends: ['ufb'], + extends: ['@ufb/eslint-config/base', '@ufb/eslint-config/nestjs'], parserOptions: { project: 'tsconfig.json', tsconfigRootDir: __dirname, diff --git a/apps/api/.gitignore b/apps/api/.gitignore deleted file mode 100644 index 22f55adc5..000000000 --- a/apps/api/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# compiled output -/dist -/node_modules - -# Logs -logs -*.log -npm-debug.log* -pnpm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# OS -.DS_Store - -# Tests -/coverage -/.nyc_output - -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index 977e8db85..c54070452 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,94 +1,95 @@ { "name": "api", "version": "0.0.1", - "description": "", - "author": "", "private": true, - "license": "UNLICENSED", "scripts": { "build": "nest build", + "clean": "git clean -xdf dist .turbo node_modules", "dev": "nest start --watch", + "format": "prettier --check \"./src/**/*.{js,cjs,mjs,ts,tsx,md,json}\"", + "lint": "eslint \"src/**/*.ts\"", + "migration:generate": "npm run typeorm -- migration:generate src/configs/modules/typeorm-config/migrations/$npm_config_name", + "migration:revert": "npm run typeorm -- migration:revert", + "migration:run": "npm run typeorm -- migration:run", "start": "nest start", - "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", + "start:dev": "nest start --watch", "start:prod": "node dist/main", - "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"", "test": "jest --detectOpenHandles --forceExit", - "test:watch": "jest --watch --detectOpenHandles", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json --runInBand --detectOpenHandles", - "typeorm": "ts-node --project ./tsconfig.json -r tsconfig-paths/register ./node_modules/typeorm/cli -d src/configs/modules/typeorm-config/typeorm-config.datasource.ts", - "migration:generate": "npm run typeorm -- migration:generate src/configs/modules/typeorm-config/migrations/$npm_config_name", - "migration:run": "npm run typeorm -- migration:run", - "migration:revert": "npm run typeorm -- migration:revert" + "test:watch": "jest --watch --detectOpenHandles", + "typecheck": "tsc --noEmit", + "typeorm": "ts-node --project ./tsconfig.json -r tsconfig-paths/register ./node_modules/typeorm/cli -d src/configs/modules/typeorm-config/typeorm-config.datasource.ts" }, + "prettier": "@ufb/prettier-config", "dependencies": { - "@fastify/static": "^6.10.2", + "@fastify/static": "^6.11.2", "@nestjs-modules/mailer": "^1.9.1", - "@nestjs/common": "^9.4.2", - "@nestjs/config": "^2.3.2", - "@nestjs/core": "^9.4.2", - "@nestjs/jwt": "^10.0.3", - "@nestjs/passport": "^9.0.3", - "@nestjs/platform-express": "^9.4.2", - "@nestjs/platform-fastify": "^9.4.2", - "@nestjs/swagger": "^6.3.0", - "@nestjs/terminus": "^9.2.2", - "@nestjs/typeorm": "^9.0.1", - "@opensearch-project/opensearch": "^1.2.0", - "@willsoto/nestjs-prometheus": "^5.1.2", - "axios": "^1.4.0", - "bcrypt": "^5.1.0", + "@nestjs/common": "^10.2.7", + "@nestjs/config": "^3.1.1", + "@nestjs/core": "^10.2.7", + "@nestjs/jwt": "^10.1.1", + "@nestjs/passport": "^10.0.2", + "@nestjs/platform-express": "^10.2.7", + "@nestjs/platform-fastify": "^10.2.7", + "@nestjs/swagger": "^7.1.13", + "@nestjs/terminus": "^10.1.1", + "@nestjs/typeorm": "^10.0.0", + "@opensearch-project/opensearch": "^2.4.0", + "@willsoto/nestjs-prometheus": "^6.0.0", + "axios": "^1.5.1", + "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "dayjs": "^1.11.9", - "exceljs": "^4.3.0", + "dayjs": "^1.11.10", + "exceljs": "^4.4.0", "fast-csv": "^4.3.6", - "mysql2": "^3.3.3", - "nestjs-cls": "^3.5.0", - "nestjs-pino": "^3.2.0", + "mysql2": "^3.6.2", + "nestjs-cls": "^3.6.0", + "nestjs-pino": "^3.5.0", "nestjs-typeorm-paginate": "^4.0.4", - "nodemailer": "^6.9.3", + "nodemailer": "^6.9.6", "passport": "^0.6.0", "passport-custom": "^1.1.1", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", - "pino-http": "^8.3.3", - "pino-pretty": "^10.0.0", - "prom-client": "^14.2.0", + "pino-http": "^8.5.0", + "pino-pretty": "^10.2.3", + "prom-client": "^15.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "source-map-support": "^0.5.21", "typeorm": "^0.3.17", "typeorm-naming-strategies": "^4.1.0", "typeorm-transactional": "^0.4.1", - "yup": "^0.32.11" + "yup": "0.32.11" }, "devDependencies": { - "@faker-js/faker": "^7.6.0", - "@nestjs/cli": "^9.5.0", - "@nestjs/schematics": "^9.2.0", - "@nestjs/testing": "^9.4.2", - "@swc-node/jest": "^1.6.5", - "@swc/core": "^1.3.62", - "@types/bcrypt": "^5.0.0", - "@types/express": "^4.17.17", - "@types/jest": "29.2.4", - "@types/node": "18.11.18", - "@types/nodemailer": "^6.4.8", - "@types/supertest": "^2.0.12", - "@ufb/tsconfig": "0.0.0", - "eslint": "^8.42.0", - "eslint-config-ufb": "0.0.0", - "jest": "29.3.1", + "@faker-js/faker": "^8.2.0", + "@nestjs/cli": "^10.1.18", + "@nestjs/schematics": "^10.0.2", + "@nestjs/testing": "^10.2.7", + "@swc-node/jest": "^1.6.8", + "@swc/core": "^1.3.93", + "@types/bcrypt": "^5.0.1", + "@types/express": "^4.17.20", + "@types/jest": "^29.5.6", + "@types/node": "20.8.7", + "@types/nodemailer": "^6.4.13", + "@types/supertest": "^2.0.15", + "@ufb/eslint-config": "^0.1.0", + "@ufb/prettier-config": "^0.1.0", + "@ufb/tsconfig": "^0.1.0", + "eslint": "^8.51.0", + "jest": "^29.7.0", "mockdate": "^3.0.5", - "prettier": "^2.8.8", "supertest": "^6.3.3", - "ts-jest": "29.0.3", - "ts-loader": "^9.4.3", + "ts-jest": "^29.1.1", + "ts-loader": "^9.5.0", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "^4.9.5" + "typescript": "^5.2.2" } } diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 6e3823e9b..4c34f2a05 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -26,7 +26,7 @@ import { OpensearchConfigModule, TypeOrmConfigModule, } from './configs/modules'; -import { mySqlConfigSchema, mysqlConfig } from './configs/mysql.config'; +import { mysqlConfig, mysqlConfigSchema } from './configs/mysql.config'; import { opensearchConfig, opensearchSchema, @@ -82,7 +82,7 @@ const domainModules = [ ...opensearchSchema.validateSync(config), ...smtpConfigSchema.validateSync(config), ...jwtConfigSchema.validateSync(config), - ...mySqlConfigSchema.validateSync(config), + ...mysqlConfigSchema.validateSync(config), }), validationOptions: { abortEarly: true }, }), @@ -92,6 +92,17 @@ const domainModules = [ autoLogging: { ignore: (req: any) => req.originalUrl === '/api/health', }, + customLogLevel: (req, res, err) => { + if (res.statusCode === 401) { + return 'silent'; + } + if (res.statusCode >= 400 && res.statusCode < 500) { + return 'warn'; + } else if (res.statusCode >= 500 || err) { + return 'error'; + } + return 'info'; + }, }, }), ClsModule.forRoot({ diff --git a/apps/api/src/common/decorators/api-ok-response-pagination.decorator.ts b/apps/api/src/common/decorators/api-ok-response-pagination.decorator.ts index 82edd97c8..3a3146b77 100644 --- a/apps/api/src/common/decorators/api-ok-response-pagination.decorator.ts +++ b/apps/api/src/common/decorators/api-ok-response-pagination.decorator.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Type, applyDecorators } from '@nestjs/common'; +import type { Type } from '@nestjs/common'; +import { applyDecorators } from '@nestjs/common'; import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger'; import { PaginationResponseDto } from '../dtos/pagination-response.dto'; diff --git a/apps/api/src/common/decorators/dto-validator.spec.ts b/apps/api/src/common/decorators/dto-validator.spec.ts index 908eefb8f..1ac260fe2 100644 --- a/apps/api/src/common/decorators/dto-validator.spec.ts +++ b/apps/api/src/common/decorators/dto-validator.spec.ts @@ -14,9 +14,6 @@ * under the License. */ -/* eslint-disable @typescript-eslint/no-unused-vars */ - -/* eslint-disable @typescript-eslint/no-empty-function */ import { IsString } from 'class-validator'; import DtoValidator from './dto-validator'; diff --git a/apps/api/src/common/decorators/is-password.spec.ts b/apps/api/src/common/decorators/is-password.spec.ts index 1770c4f2b..d3fccb2a2 100644 --- a/apps/api/src/common/decorators/is-password.spec.ts +++ b/apps/api/src/common/decorators/is-password.spec.ts @@ -26,8 +26,8 @@ class IsPasswordTest { describe('IsPassword decorator', () => { it('', () => { const instance = new IsPasswordTest(); - instance.password = faker.datatype.string( - faker.datatype.number({ min: 8, max: 15 }), + instance.password = faker.string.sample( + faker.number.int({ min: 8, max: 15 }), ); const validator = new Validator(); validator.validate(instance).then((errors) => { @@ -36,8 +36,8 @@ describe('IsPassword decorator', () => { }); it('minLength', () => { const instance = new IsPasswordTest(); - instance.password = faker.datatype.string( - faker.datatype.number({ min: 0, max: 7 }), + instance.password = faker.string.sample( + faker.number.int({ min: 0, max: 7 }), ); const validator = new Validator(); validator.validate(instance).then((errors) => { @@ -48,7 +48,7 @@ describe('IsPassword decorator', () => { it('isString', () => { const instance = new IsPasswordTest(); - instance.password = faker.datatype.number({ min: 10000000, max: 99999999 }); + instance.password = faker.number.int({ min: 10000000, max: 99999999 }); const validator = new Validator(); validator.validate(instance).then((errors) => { @@ -59,7 +59,7 @@ describe('IsPassword decorator', () => { it('isString, minLength', () => { const instance = new IsPasswordTest(); - instance.password = faker.datatype.number({ min: 0, max: 9999999 }); + instance.password = faker.number.int({ min: 0, max: 9999999 }); const validator = new Validator(); validator.validate(instance).then((errors) => { expect(errors.length).toEqual(1); diff --git a/apps/api/src/common/dtos/pagination-response.dto.ts b/apps/api/src/common/dtos/pagination-response.dto.ts index 3f7f73e1f..0ff16b358 100644 --- a/apps/api/src/common/dtos/pagination-response.dto.ts +++ b/apps/api/src/common/dtos/pagination-response.dto.ts @@ -15,7 +15,7 @@ */ import { ApiProperty } from '@nestjs/swagger'; import { Expose, Type } from 'class-transformer'; -import { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; +import type { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; class PaginationMetaDto implements IPaginationMeta { @ApiProperty() diff --git a/apps/api/src/common/filters/http-exception.filter.ts b/apps/api/src/common/filters/http-exception.filter.ts index 2ba06ed18..2c4a771b2 100644 --- a/apps/api/src/common/filters/http-exception.filter.ts +++ b/apps/api/src/common/filters/http-exception.filter.ts @@ -13,14 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { - ArgumentsHost, - Catch, - ExceptionFilter, - HttpException, - Logger, -} from '@nestjs/common'; -import { FastifyReply, FastifyRequest } from 'fastify'; +import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common'; +import { Catch, HttpException, Logger } from '@nestjs/common'; +import type { FastifyReply, FastifyRequest } from 'fastify'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { @@ -45,12 +40,5 @@ export class HttpExceptionFilter implements ExceptionFilter { path: request.url, }); } - - this.logger.error( - JSON.stringify({ - request: { method: request.method, url: request.url }, - exception, - }), - ); } } diff --git a/apps/api/src/common/repositories/dtos/get-data.dto.ts b/apps/api/src/common/repositories/dtos/get-data.dto.ts index 30f67ae9e..46c0d5828 100644 --- a/apps/api/src/common/repositories/dtos/get-data.dto.ts +++ b/apps/api/src/common/repositories/dtos/get-data.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { PaginationDto } from '@/common/dtos'; -import { OsQueryDto } from '@/domains/feedback/dtos/os-query.dto'; +import type { OsQueryDto } from '@/domains/feedback/dtos/os-query.dto'; export class GetDataDto extends PaginationDto { index: string; diff --git a/apps/api/src/common/repositories/dtos/scroll.dto.ts b/apps/api/src/common/repositories/dtos/scroll.dto.ts index 9e9e3dc6a..274a2cbfd 100644 --- a/apps/api/src/common/repositories/dtos/scroll.dto.ts +++ b/apps/api/src/common/repositories/dtos/scroll.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { OsQueryDto } from '@/domains/feedback/dtos/os-query.dto'; +import type { OsQueryDto } from '@/domains/feedback/dtos/os-query.dto'; export class ScrollDto { index: string; diff --git a/apps/api/src/common/repositories/opensearch.repository.spec.ts b/apps/api/src/common/repositories/opensearch.repository.spec.ts index ee149e1c3..537699d03 100644 --- a/apps/api/src/common/repositories/opensearch.repository.spec.ts +++ b/apps/api/src/common/repositories/opensearch.repository.spec.ts @@ -19,10 +19,9 @@ import { NotFoundException, } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { Client } from '@opensearch-project/opensearch'; - -import { getMockProvider } from '@/utils/test-utils'; +import type { Client } from '@opensearch-project/opensearch'; +import { getMockProvider } from '@/test-utils/util-functions'; import { CreateDataDto, PutMappingsDto } from './dtos'; import { OpensearchRepository } from './opensearch.repository'; @@ -48,6 +47,17 @@ const OpensearchRepositoryProviders = [ getMockProvider('OPENSEARCH_CLIENT', MockClient), ]; +const COMPLICATE_JSON = { + KEY1: 'VALUE1', + KEY2: 'VALUE2', +}; + +const MAPPING_JSON = { + KEY1: { + type: 'text', + }, +}; + describe('Opensearch Repository Test suite', () => { let osRepo: OpensearchRepository; let osClient: Client; @@ -61,7 +71,7 @@ describe('Opensearch Repository Test suite', () => { describe('create index', () => { it('positive case', async () => { - const index = faker.datatype.number().toString(); + const index = faker.number.int().toString(); const indexName = 'channel_' + index; jest.spyOn(osClient.indices, 'create'); jest.spyOn(osClient.indices, 'putAlias'); @@ -104,8 +114,8 @@ describe('Opensearch Repository Test suite', () => { describe('putMappings', () => { it('putting mappings succeeds with an existent index', async () => { const dto = new PutMappingsDto(); - dto.index = faker.datatype.number().toString(); - dto.mappings = JSON.parse(faker.datatype.json()); + dto.index = faker.number.int().toString(); + dto.mappings = MAPPING_JSON; jest .spyOn(osClient.indices, 'exists') .mockResolvedValue({ body: true } as never); @@ -122,8 +132,8 @@ describe('Opensearch Repository Test suite', () => { }); it('putting mappings fails with a nonexistent index', async () => { const dto = new PutMappingsDto(); - dto.index = faker.datatype.number().toString(); - dto.mappings = JSON.parse(faker.datatype.json()); + dto.index = faker.number.int().toString(); + dto.mappings = MAPPING_JSON; jest .spyOn(osClient.indices, 'exists') .mockResolvedValue({ body: false } as never); @@ -140,12 +150,12 @@ describe('Opensearch Repository Test suite', () => { describe('createData', () => { it('creating data succeeds with valid inputs', async () => { - const index = faker.datatype.number().toString(); - const id = faker.datatype.number().toString(); + const index = faker.number.int().toString(); + const id = faker.number.int().toString(); const dto = new CreateDataDto(); dto.id = id; dto.index = index; - dto.data = JSON.parse(faker.datatype.json()); + dto.data = COMPLICATE_JSON; jest .spyOn(osClient.indices, 'exists') .mockResolvedValue({ body: true } as never); @@ -177,18 +187,18 @@ describe('Opensearch Repository Test suite', () => { }); }); it('creating data fails with an invalid index', async () => { - const invalidIndex = faker.datatype.number().toString(); - const id = faker.datatype.number().toString(); + const invalidIndex = faker.number.int().toString(); + const id = faker.number.int().toString(); const dto = new CreateDataDto(); dto.id = id; dto.index = invalidIndex; - dto.data = JSON.parse(faker.datatype.json()); + dto.data = COMPLICATE_JSON; jest .spyOn(osClient.indices, 'exists') .mockResolvedValue({ body: false } as never); jest.spyOn(osClient.indices, 'getMapping').mockResolvedValue({ body: { - ['channel_' + faker.datatype.number().toString()]: { + ['channel_' + faker.number.int().toString()]: { mappings: { properties: dto.data, }, @@ -210,9 +220,9 @@ describe('Opensearch Repository Test suite', () => { expect(osClient.index).not.toBeCalled(); }); it('creating data fails with invalid data', async () => { - const index = faker.datatype.number().toString(); - const id = faker.datatype.number().toString(); - const data = JSON.parse(faker.datatype.json()); + const index = faker.number.int().toString(); + const id = faker.number.int().toString(); + const data = COMPLICATE_JSON; const dto = new CreateDataDto(); dto.id = id; dto.index = index; @@ -248,21 +258,15 @@ describe('Opensearch Repository Test suite', () => { }); }); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('getData', () => {}); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('scroll', () => {}); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('updateData', () => {}); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('deleteBulkData', () => {}); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('deleteIndex', () => {}); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('getTotal', () => {}); }); diff --git a/apps/api/src/common/repositories/opensearch.repository.ts b/apps/api/src/common/repositories/opensearch.repository.ts index cb4a3d34f..c33b6f23c 100644 --- a/apps/api/src/common/repositories/opensearch.repository.ts +++ b/apps/api/src/common/repositories/opensearch.repository.ts @@ -21,7 +21,7 @@ import { } from '@nestjs/common'; import { Client, errors } from '@opensearch-project/opensearch'; -import { +import type { CreateDataDto, CreateIndexDto, DeleteBulkDataDto, diff --git a/apps/api/src/common/validators/array-distinct.ts b/apps/api/src/common/validators/array-distinct.ts index 3773eb966..bd8c7cb99 100644 --- a/apps/api/src/common/validators/array-distinct.ts +++ b/apps/api/src/common/validators/array-distinct.ts @@ -13,11 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { - ValidationArguments, - ValidationOptions, - registerDecorator, -} from 'class-validator'; +import type { ValidationArguments, ValidationOptions } from 'class-validator'; +import { registerDecorator } from 'class-validator'; export const ArrayDistinct = ( property?: string, diff --git a/apps/api/src/configs/app.config.ts b/apps/api/src/configs/app.config.ts index 37dfed804..50648b728 100644 --- a/apps/api/src/configs/app.config.ts +++ b/apps/api/src/configs/app.config.ts @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ + import { registerAs } from '@nestjs/config'; import * as yup from 'yup'; diff --git a/apps/api/src/configs/modules/mailer-config/mailer-config.module.ts b/apps/api/src/configs/modules/mailer-config/mailer-config.module.ts index 238ce38e7..ec8c612aa 100644 --- a/apps/api/src/configs/modules/mailer-config/mailer-config.module.ts +++ b/apps/api/src/configs/modules/mailer-config/mailer-config.module.ts @@ -18,7 +18,7 @@ import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handleba import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; -import { ConfigServiceType } from '@/types/config-service.type'; +import type { ConfigServiceType } from '@/types/config-service.type'; @Module({ imports: [ diff --git a/apps/api/src/configs/modules/opensearch-config/opensearch-config.module.ts b/apps/api/src/configs/modules/opensearch-config/opensearch-config.module.ts index f2d9e428a..435dbb952 100644 --- a/apps/api/src/configs/modules/opensearch-config/opensearch-config.module.ts +++ b/apps/api/src/configs/modules/opensearch-config/opensearch-config.module.ts @@ -18,7 +18,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { Client } from '@opensearch-project/opensearch'; import * as dotenv from 'dotenv'; -import { ConfigServiceType } from '@/types/config-service.type'; +import type { ConfigServiceType } from '@/types/config-service.type'; dotenv.config(); diff --git a/apps/api/src/configs/modules/typeorm-config/migrations/1692159572819-init.ts b/apps/api/src/configs/modules/typeorm-config/migrations/1692159572819-init.ts index 9d636dc93..a1e9615b0 100644 --- a/apps/api/src/configs/modules/typeorm-config/migrations/1692159572819-init.ts +++ b/apps/api/src/configs/modules/typeorm-config/migrations/1692159572819-init.ts @@ -1,86 +1,199 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; export class Init1692159572819 implements MigrationInterface { - name = 'Init1692159572819' + name = 'Init1692159572819'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE \`tenant\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`site_name\` varchar(50) NOT NULL, \`description\` varchar(255) NULL, \`use_email\` tinyint NOT NULL DEFAULT 1, \`is_private\` tinyint NOT NULL DEFAULT 0, \`is_restrict_domain\` tinyint NOT NULL DEFAULT 0, \`allow_domains\` text NULL, \`use_o_auth\` tinyint NOT NULL DEFAULT 0, \`oauth_config\` json NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`issues\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`status\` enum ('INIT', 'ON_REVIEW', 'IN_PROGRESS', 'RESOLVED', 'PENDING') NOT NULL DEFAULT 'INIT', \`external_issue_id\` varchar(255) NULL, \`feedback_count\` int NOT NULL DEFAULT '0', \`project_id\` int NULL, INDEX \`IDX_b7fd6df20da19c630741ea9045\` (\`status\`), INDEX \`IDX_db94fcc9ef9f968b43ec5d2b2a\` (\`feedback_count\`), INDEX \`IDX_8e64309f790aa4270b955a9947\` (\`project_id\`, \`created_at\`), UNIQUE INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`feedbacks\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`raw_data\` json NOT NULL, \`additional_data\` json NULL, \`channel_id\` int NULL, INDEX \`IDX_a640975f8ccf17d9337d4ff828\` (\`created_at\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`options\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`key\` varchar(255) NOT NULL, \`field_id\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`fields\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`key\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`format\` enum ('text', 'keyword', 'number', 'boolean', 'select', 'multiSelect', 'date') NOT NULL, \`type\` enum ('DEFAULT', 'ADMIN', 'API') NOT NULL, \`status\` enum ('ACTIVE', 'INACTIVE') NOT NULL, \`channel_id\` int NULL, INDEX \`IDX_4b2181db660323e7ae856adeae\` (\`created_at\`), UNIQUE INDEX \`field-name-unique\` (\`name\`, \`channel_id\`), UNIQUE INDEX \`field-key-unique\` (\`key\`, \`channel_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`channels\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`project_id\` int NULL, INDEX \`IDX_1233531abfb8d56d2a15050143\` (\`name\`, \`created_at\`), UNIQUE INDEX \`project-name-unique\` (\`name\`, \`project_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`projects\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`tenant_id\` int NULL, UNIQUE INDEX \`IDX_2187088ab5ef2a918473cb9900\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`roles\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`permissions\` text NOT NULL, \`project_id\` int NULL, INDEX \`IDX_f4f2789197a3cbbc0182396b26\` (\`name\`, \`project_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`members\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`role_id\` int NULL, \`user_id\` int NULL, UNIQUE INDEX \`IDX_858f5ec01bcfe14ab3f2a328dc\` (\`role_id\`, \`user_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`users\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`email\` varchar(320) NULL, \`name\` varchar(255) NULL, \`department\` varchar(255) NULL, \`state\` enum ('Active', 'Blocked') NOT NULL DEFAULT 'Active', \`hash_password\` varchar(255) NULL, \`type\` enum ('SUPER', 'GENERAL') NOT NULL DEFAULT 'GENERAL', \`sign_up_method\` enum ('EMAIL', 'OAUTH') NOT NULL DEFAULT 'EMAIL', UNIQUE INDEX \`IDX_1301b11757c5b489adc8bc05e4\` (\`email\`, \`sign_up_method\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`histories\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`entity_name\` enum ('ApiKey', 'Channel', 'Feedback', 'Field', 'IssueTracker', 'Issue', 'Member', 'Option', 'Project', 'Role', 'Tenant', 'User', 'FeedbackIssue', 'Code') NOT NULL, \`entity_id\` decimal NOT NULL, \`action\` enum ('Create', 'Update', 'Delete', 'SoftDelete', 'Download', 'Recover') NOT NULL, \`entity\` json NOT NULL, \`user_id\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`codes\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`type\` enum ('EMAIL_VEIRIFICATION', 'RESET_PASSWORD', 'USER_INVITATION') NOT NULL, \`key\` varchar(255) NOT NULL, \`code\` varchar(255) NOT NULL, \`data\` varchar(255) NULL, \`is_verified\` tinyint NOT NULL DEFAULT 0, \`expired_at\` datetime NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`api_keys\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`value\` varchar(255) NOT NULL, \`project_id\` int NULL, UNIQUE INDEX \`IDX_2662a95fc4dd64493ca686a82f\` (\`value\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`issue_trackers\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`data\` json NULL, \`project_id\` int NULL, UNIQUE INDEX \`REL_0d000918b0c670b7d2488257dd\` (\`project_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); - await queryRunner.query(`CREATE TABLE \`feedbacks_issues_issues\` (\`feedbacks_id\` int NOT NULL, \`issues_id\` int NOT NULL, INDEX \`IDX_3435079c319679ba3aaccd806b\` (\`feedbacks_id\`), INDEX \`IDX_6d6f24cf306a31c0af7b50973a\` (\`issues_id\`), PRIMARY KEY (\`feedbacks_id\`, \`issues_id\`)) ENGINE=InnoDB`); - await queryRunner.query(`ALTER TABLE \`issues\` ADD CONSTRAINT \`FK_11f35e8296e10c229e7b68c68d4\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`feedbacks\` ADD CONSTRAINT \`FK_4adbe5e6c46eba8a93a0265a078\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`options\` ADD CONSTRAINT \`FK_dc520ce6f54769336c4afa5e9b9\` FOREIGN KEY (\`field_id\`) REFERENCES \`fields\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`fields\` ADD CONSTRAINT \`FK_da856f4b147eb542917c5968c43\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`channels\` ADD CONSTRAINT \`FK_63c4e21cafd9504a7c139144d1c\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`projects\` ADD CONSTRAINT \`FK_7393a03ef67e2ea91b81faa95dd\` FOREIGN KEY (\`tenant_id\`) REFERENCES \`tenant\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`roles\` ADD CONSTRAINT \`FK_cb48212dfe65dfe431d486034d2\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`members\` ADD CONSTRAINT \`FK_274c5ebb3c595f5a56f1f8fba9a\` FOREIGN KEY (\`role_id\`) REFERENCES \`roles\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`members\` ADD CONSTRAINT \`FK_da404b5fd9c390e25338996e2d1\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`api_keys\` ADD CONSTRAINT \`FK_f5de07dbb229225e2be643ff3d0\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`issue_trackers\` ADD CONSTRAINT \`FK_0d000918b0c670b7d2488257dd7\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE \`feedbacks_issues_issues\` ADD CONSTRAINT \`FK_3435079c319679ba3aaccd806b1\` FOREIGN KEY (\`feedbacks_id\`) REFERENCES \`feedbacks\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`); - await queryRunner.query(`ALTER TABLE \`feedbacks_issues_issues\` ADD CONSTRAINT \`FK_6d6f24cf306a31c0af7b50973ab\` FOREIGN KEY (\`issues_id\`) REFERENCES \`issues\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE \`feedbacks_issues_issues\` DROP FOREIGN KEY \`FK_6d6f24cf306a31c0af7b50973ab\``); - await queryRunner.query(`ALTER TABLE \`feedbacks_issues_issues\` DROP FOREIGN KEY \`FK_3435079c319679ba3aaccd806b1\``); - await queryRunner.query(`ALTER TABLE \`issue_trackers\` DROP FOREIGN KEY \`FK_0d000918b0c670b7d2488257dd7\``); - await queryRunner.query(`ALTER TABLE \`api_keys\` DROP FOREIGN KEY \`FK_f5de07dbb229225e2be643ff3d0\``); - await queryRunner.query(`ALTER TABLE \`members\` DROP FOREIGN KEY \`FK_da404b5fd9c390e25338996e2d1\``); - await queryRunner.query(`ALTER TABLE \`members\` DROP FOREIGN KEY \`FK_274c5ebb3c595f5a56f1f8fba9a\``); - await queryRunner.query(`ALTER TABLE \`roles\` DROP FOREIGN KEY \`FK_cb48212dfe65dfe431d486034d2\``); - await queryRunner.query(`ALTER TABLE \`projects\` DROP FOREIGN KEY \`FK_7393a03ef67e2ea91b81faa95dd\``); - await queryRunner.query(`ALTER TABLE \`channels\` DROP FOREIGN KEY \`FK_63c4e21cafd9504a7c139144d1c\``); - await queryRunner.query(`ALTER TABLE \`fields\` DROP FOREIGN KEY \`FK_da856f4b147eb542917c5968c43\``); - await queryRunner.query(`ALTER TABLE \`options\` DROP FOREIGN KEY \`FK_dc520ce6f54769336c4afa5e9b9\``); - await queryRunner.query(`ALTER TABLE \`feedbacks\` DROP FOREIGN KEY \`FK_4adbe5e6c46eba8a93a0265a078\``); - await queryRunner.query(`ALTER TABLE \`issues\` DROP FOREIGN KEY \`FK_11f35e8296e10c229e7b68c68d4\``); - await queryRunner.query(`DROP INDEX \`IDX_6d6f24cf306a31c0af7b50973a\` ON \`feedbacks_issues_issues\``); - await queryRunner.query(`DROP INDEX \`IDX_3435079c319679ba3aaccd806b\` ON \`feedbacks_issues_issues\``); - await queryRunner.query(`DROP TABLE \`feedbacks_issues_issues\``); - await queryRunner.query(`DROP INDEX \`REL_0d000918b0c670b7d2488257dd\` ON \`issue_trackers\``); - await queryRunner.query(`DROP TABLE \`issue_trackers\``); - await queryRunner.query(`DROP INDEX \`IDX_2662a95fc4dd64493ca686a82f\` ON \`api_keys\``); - await queryRunner.query(`DROP TABLE \`api_keys\``); - await queryRunner.query(`DROP TABLE \`codes\``); - await queryRunner.query(`DROP TABLE \`histories\``); - await queryRunner.query(`DROP INDEX \`IDX_1301b11757c5b489adc8bc05e4\` ON \`users\``); - await queryRunner.query(`DROP TABLE \`users\``); - await queryRunner.query(`DROP INDEX \`IDX_858f5ec01bcfe14ab3f2a328dc\` ON \`members\``); - await queryRunner.query(`DROP TABLE \`members\``); - await queryRunner.query(`DROP INDEX \`IDX_f4f2789197a3cbbc0182396b26\` ON \`roles\``); - await queryRunner.query(`DROP TABLE \`roles\``); - await queryRunner.query(`DROP INDEX \`IDX_2187088ab5ef2a918473cb9900\` ON \`projects\``); - await queryRunner.query(`DROP TABLE \`projects\``); - await queryRunner.query(`DROP INDEX \`project-name-unique\` ON \`channels\``); - await queryRunner.query(`DROP INDEX \`IDX_1233531abfb8d56d2a15050143\` ON \`channels\``); - await queryRunner.query(`DROP TABLE \`channels\``); - await queryRunner.query(`DROP INDEX \`field-key-unique\` ON \`fields\``); - await queryRunner.query(`DROP INDEX \`field-name-unique\` ON \`fields\``); - await queryRunner.query(`DROP INDEX \`IDX_4b2181db660323e7ae856adeae\` ON \`fields\``); - await queryRunner.query(`DROP TABLE \`fields\``); - await queryRunner.query(`DROP TABLE \`options\``); - await queryRunner.query(`DROP INDEX \`IDX_a640975f8ccf17d9337d4ff828\` ON \`feedbacks\``); - await queryRunner.query(`DROP TABLE \`feedbacks\``); - await queryRunner.query(`DROP INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` ON \`issues\``); - await queryRunner.query(`DROP INDEX \`IDX_8e64309f790aa4270b955a9947\` ON \`issues\``); - await queryRunner.query(`DROP INDEX \`IDX_db94fcc9ef9f968b43ec5d2b2a\` ON \`issues\``); - await queryRunner.query(`DROP INDEX \`IDX_b7fd6df20da19c630741ea9045\` ON \`issues\``); - await queryRunner.query(`DROP TABLE \`issues\``); - await queryRunner.query(`DROP TABLE \`tenant\``); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`tenant\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`site_name\` varchar(50) NOT NULL, \`description\` varchar(255) NULL, \`use_email\` tinyint NOT NULL DEFAULT 1, \`is_private\` tinyint NOT NULL DEFAULT 0, \`is_restrict_domain\` tinyint NOT NULL DEFAULT 0, \`allow_domains\` text NULL, \`use_o_auth\` tinyint NOT NULL DEFAULT 0, \`oauth_config\` json NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`issues\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`status\` enum ('INIT', 'ON_REVIEW', 'IN_PROGRESS', 'RESOLVED', 'PENDING') NOT NULL DEFAULT 'INIT', \`external_issue_id\` varchar(255) NULL, \`feedback_count\` int NOT NULL DEFAULT '0', \`project_id\` int NULL, INDEX \`IDX_b7fd6df20da19c630741ea9045\` (\`status\`), INDEX \`IDX_db94fcc9ef9f968b43ec5d2b2a\` (\`feedback_count\`), INDEX \`IDX_8e64309f790aa4270b955a9947\` (\`project_id\`, \`created_at\`), UNIQUE INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`feedbacks\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`raw_data\` json NOT NULL, \`additional_data\` json NULL, \`channel_id\` int NULL, INDEX \`IDX_a640975f8ccf17d9337d4ff828\` (\`created_at\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`options\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`key\` varchar(255) NOT NULL, \`field_id\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`fields\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`key\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`format\` enum ('text', 'keyword', 'number', 'boolean', 'select', 'multiSelect', 'date') NOT NULL, \`type\` enum ('DEFAULT', 'ADMIN', 'API') NOT NULL, \`status\` enum ('ACTIVE', 'INACTIVE') NOT NULL, \`channel_id\` int NULL, INDEX \`IDX_4b2181db660323e7ae856adeae\` (\`created_at\`), UNIQUE INDEX \`field-name-unique\` (\`name\`, \`channel_id\`), UNIQUE INDEX \`field-key-unique\` (\`key\`, \`channel_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`channels\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`project_id\` int NULL, INDEX \`IDX_1233531abfb8d56d2a15050143\` (\`name\`, \`created_at\`), UNIQUE INDEX \`project-name-unique\` (\`name\`, \`project_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`projects\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`description\` varchar(255) NULL, \`tenant_id\` int NULL, UNIQUE INDEX \`IDX_2187088ab5ef2a918473cb9900\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`roles\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`name\` varchar(255) NOT NULL, \`permissions\` text NOT NULL, \`project_id\` int NULL, INDEX \`IDX_f4f2789197a3cbbc0182396b26\` (\`name\`, \`project_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`members\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`role_id\` int NULL, \`user_id\` int NULL, UNIQUE INDEX \`IDX_858f5ec01bcfe14ab3f2a328dc\` (\`role_id\`, \`user_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`users\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`email\` varchar(320) NULL, \`name\` varchar(255) NULL, \`department\` varchar(255) NULL, \`state\` enum ('Active', 'Blocked') NOT NULL DEFAULT 'Active', \`hash_password\` varchar(255) NULL, \`type\` enum ('SUPER', 'GENERAL') NOT NULL DEFAULT 'GENERAL', \`sign_up_method\` enum ('EMAIL', 'OAUTH') NOT NULL DEFAULT 'EMAIL', UNIQUE INDEX \`IDX_1301b11757c5b489adc8bc05e4\` (\`email\`, \`sign_up_method\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`histories\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`entity_name\` enum ('ApiKey', 'Channel', 'Feedback', 'Field', 'IssueTracker', 'Issue', 'Member', 'Option', 'Project', 'Role', 'Tenant', 'User', 'FeedbackIssue', 'Code') NOT NULL, \`entity_id\` decimal NOT NULL, \`action\` enum ('Create', 'Update', 'Delete', 'SoftDelete', 'Download', 'Recover') NOT NULL, \`entity\` json NOT NULL, \`user_id\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`codes\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`type\` enum ('EMAIL_VEIRIFICATION', 'RESET_PASSWORD', 'USER_INVITATION') NOT NULL, \`key\` varchar(255) NOT NULL, \`code\` varchar(255) NOT NULL, \`data\` varchar(255) NULL, \`is_verified\` tinyint NOT NULL DEFAULT 0, \`expired_at\` datetime NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`api_keys\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`value\` varchar(255) NOT NULL, \`project_id\` int NULL, UNIQUE INDEX \`IDX_2662a95fc4dd64493ca686a82f\` (\`value\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`issue_trackers\` (\`id\` int NOT NULL AUTO_INCREMENT, \`created_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updated_at\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`deleted_at\` datetime(6) NULL, \`data\` json NULL, \`project_id\` int NULL, UNIQUE INDEX \`REL_0d000918b0c670b7d2488257dd\` (\`project_id\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `CREATE TABLE \`feedbacks_issues_issues\` (\`feedbacks_id\` int NOT NULL, \`issues_id\` int NOT NULL, INDEX \`IDX_3435079c319679ba3aaccd806b\` (\`feedbacks_id\`), INDEX \`IDX_6d6f24cf306a31c0af7b50973a\` (\`issues_id\`), PRIMARY KEY (\`feedbacks_id\`, \`issues_id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `ALTER TABLE \`issues\` ADD CONSTRAINT \`FK_11f35e8296e10c229e7b68c68d4\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`feedbacks\` ADD CONSTRAINT \`FK_4adbe5e6c46eba8a93a0265a078\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`options\` ADD CONSTRAINT \`FK_dc520ce6f54769336c4afa5e9b9\` FOREIGN KEY (\`field_id\`) REFERENCES \`fields\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`fields\` ADD CONSTRAINT \`FK_da856f4b147eb542917c5968c43\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`channels\` ADD CONSTRAINT \`FK_63c4e21cafd9504a7c139144d1c\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`projects\` ADD CONSTRAINT \`FK_7393a03ef67e2ea91b81faa95dd\` FOREIGN KEY (\`tenant_id\`) REFERENCES \`tenant\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`roles\` ADD CONSTRAINT \`FK_cb48212dfe65dfe431d486034d2\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`members\` ADD CONSTRAINT \`FK_274c5ebb3c595f5a56f1f8fba9a\` FOREIGN KEY (\`role_id\`) REFERENCES \`roles\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`members\` ADD CONSTRAINT \`FK_da404b5fd9c390e25338996e2d1\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`api_keys\` ADD CONSTRAINT \`FK_f5de07dbb229225e2be643ff3d0\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`issue_trackers\` ADD CONSTRAINT \`FK_0d000918b0c670b7d2488257dd7\` FOREIGN KEY (\`project_id\`) REFERENCES \`projects\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE \`feedbacks_issues_issues\` ADD CONSTRAINT \`FK_3435079c319679ba3aaccd806b1\` FOREIGN KEY (\`feedbacks_id\`) REFERENCES \`feedbacks\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE \`feedbacks_issues_issues\` ADD CONSTRAINT \`FK_6d6f24cf306a31c0af7b50973ab\` FOREIGN KEY (\`issues_id\`) REFERENCES \`issues\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`feedbacks_issues_issues\` DROP FOREIGN KEY \`FK_6d6f24cf306a31c0af7b50973ab\``, + ); + await queryRunner.query( + `ALTER TABLE \`feedbacks_issues_issues\` DROP FOREIGN KEY \`FK_3435079c319679ba3aaccd806b1\``, + ); + await queryRunner.query( + `ALTER TABLE \`issue_trackers\` DROP FOREIGN KEY \`FK_0d000918b0c670b7d2488257dd7\``, + ); + await queryRunner.query( + `ALTER TABLE \`api_keys\` DROP FOREIGN KEY \`FK_f5de07dbb229225e2be643ff3d0\``, + ); + await queryRunner.query( + `ALTER TABLE \`members\` DROP FOREIGN KEY \`FK_da404b5fd9c390e25338996e2d1\``, + ); + await queryRunner.query( + `ALTER TABLE \`members\` DROP FOREIGN KEY \`FK_274c5ebb3c595f5a56f1f8fba9a\``, + ); + await queryRunner.query( + `ALTER TABLE \`roles\` DROP FOREIGN KEY \`FK_cb48212dfe65dfe431d486034d2\``, + ); + await queryRunner.query( + `ALTER TABLE \`projects\` DROP FOREIGN KEY \`FK_7393a03ef67e2ea91b81faa95dd\``, + ); + await queryRunner.query( + `ALTER TABLE \`channels\` DROP FOREIGN KEY \`FK_63c4e21cafd9504a7c139144d1c\``, + ); + await queryRunner.query( + `ALTER TABLE \`fields\` DROP FOREIGN KEY \`FK_da856f4b147eb542917c5968c43\``, + ); + await queryRunner.query( + `ALTER TABLE \`options\` DROP FOREIGN KEY \`FK_dc520ce6f54769336c4afa5e9b9\``, + ); + await queryRunner.query( + `ALTER TABLE \`feedbacks\` DROP FOREIGN KEY \`FK_4adbe5e6c46eba8a93a0265a078\``, + ); + await queryRunner.query( + `ALTER TABLE \`issues\` DROP FOREIGN KEY \`FK_11f35e8296e10c229e7b68c68d4\``, + ); + await queryRunner.query( + `DROP INDEX \`IDX_6d6f24cf306a31c0af7b50973a\` ON \`feedbacks_issues_issues\``, + ); + await queryRunner.query( + `DROP INDEX \`IDX_3435079c319679ba3aaccd806b\` ON \`feedbacks_issues_issues\``, + ); + await queryRunner.query(`DROP TABLE \`feedbacks_issues_issues\``); + await queryRunner.query( + `DROP INDEX \`REL_0d000918b0c670b7d2488257dd\` ON \`issue_trackers\``, + ); + await queryRunner.query(`DROP TABLE \`issue_trackers\``); + await queryRunner.query( + `DROP INDEX \`IDX_2662a95fc4dd64493ca686a82f\` ON \`api_keys\``, + ); + await queryRunner.query(`DROP TABLE \`api_keys\``); + await queryRunner.query(`DROP TABLE \`codes\``); + await queryRunner.query(`DROP TABLE \`histories\``); + await queryRunner.query( + `DROP INDEX \`IDX_1301b11757c5b489adc8bc05e4\` ON \`users\``, + ); + await queryRunner.query(`DROP TABLE \`users\``); + await queryRunner.query( + `DROP INDEX \`IDX_858f5ec01bcfe14ab3f2a328dc\` ON \`members\``, + ); + await queryRunner.query(`DROP TABLE \`members\``); + await queryRunner.query( + `DROP INDEX \`IDX_f4f2789197a3cbbc0182396b26\` ON \`roles\``, + ); + await queryRunner.query(`DROP TABLE \`roles\``); + await queryRunner.query( + `DROP INDEX \`IDX_2187088ab5ef2a918473cb9900\` ON \`projects\``, + ); + await queryRunner.query(`DROP TABLE \`projects\``); + await queryRunner.query( + `DROP INDEX \`project-name-unique\` ON \`channels\``, + ); + await queryRunner.query( + `DROP INDEX \`IDX_1233531abfb8d56d2a15050143\` ON \`channels\``, + ); + await queryRunner.query(`DROP TABLE \`channels\``); + await queryRunner.query(`DROP INDEX \`field-key-unique\` ON \`fields\``); + await queryRunner.query(`DROP INDEX \`field-name-unique\` ON \`fields\``); + await queryRunner.query( + `DROP INDEX \`IDX_4b2181db660323e7ae856adeae\` ON \`fields\``, + ); + await queryRunner.query(`DROP TABLE \`fields\``); + await queryRunner.query(`DROP TABLE \`options\``); + await queryRunner.query( + `DROP INDEX \`IDX_a640975f8ccf17d9337d4ff828\` ON \`feedbacks\``, + ); + await queryRunner.query(`DROP TABLE \`feedbacks\``); + await queryRunner.query( + `DROP INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` ON \`issues\``, + ); + await queryRunner.query( + `DROP INDEX \`IDX_8e64309f790aa4270b955a9947\` ON \`issues\``, + ); + await queryRunner.query( + `DROP INDEX \`IDX_db94fcc9ef9f968b43ec5d2b2a\` ON \`issues\``, + ); + await queryRunner.query( + `DROP INDEX \`IDX_b7fd6df20da19c630741ea9045\` ON \`issues\``, + ); + await queryRunner.query(`DROP TABLE \`issues\``); + await queryRunner.query(`DROP TABLE \`tenant\``); + } } diff --git a/apps/api/src/configs/modules/typeorm-config/migrations/1692690482919-issue-name-unique.ts b/apps/api/src/configs/modules/typeorm-config/migrations/1692690482919-issue-name-unique.ts index 0c835d877..5cad8c4b4 100644 --- a/apps/api/src/configs/modules/typeorm-config/migrations/1692690482919-issue-name-unique.ts +++ b/apps/api/src/configs/modules/typeorm-config/migrations/1692690482919-issue-name-unique.ts @@ -1,16 +1,21 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; export class IssueNameUnique1692690482919 implements MigrationInterface { - name = 'IssueNameUnique1692690482919' + name = 'IssueNameUnique1692690482919'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` ON \`issues\``); - await queryRunner.query(`CREATE UNIQUE INDEX \`issue-name-unique\` ON \`issues\` (\`name\`, \`project_id\`)`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP INDEX \`issue-name-unique\` ON \`issues\``); - await queryRunner.query(`CREATE UNIQUE INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` ON \`issues\` (\`name\`)`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DROP INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` ON \`issues\``, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX \`issue-name-unique\` ON \`issues\` (\`name\`, \`project_id\`)`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX \`issue-name-unique\` ON \`issues\``); + await queryRunner.query( + `CREATE UNIQUE INDEX \`IDX_b711d3eb6f21e35f5a0623dbe2\` ON \`issues\` (\`name\`)`, + ); + } } diff --git a/apps/api/src/configs/modules/typeorm-config/typeorm-config.datasource.ts b/apps/api/src/configs/modules/typeorm-config/typeorm-config.datasource.ts index e028bdb53..83f60c840 100644 --- a/apps/api/src/configs/modules/typeorm-config/typeorm-config.datasource.ts +++ b/apps/api/src/configs/modules/typeorm-config/typeorm-config.datasource.ts @@ -15,14 +15,14 @@ */ import { ConfigService } from '@nestjs/config'; import * as dotenv from 'dotenv'; -import { DataSource, DataSourceOptions } from 'typeorm'; - -import { mySqlConfigSchema, mysqlConfig } from '@/configs/mysql.config'; +import type { DataSourceOptions } from 'typeorm'; +import { DataSource } from 'typeorm'; +import { mysqlConfig, mysqlConfigSchema } from '@/configs/mysql.config'; import { TypeOrmConfigService } from './typeorm-config.service'; dotenv.config(); -process.env = mySqlConfigSchema.validateSync(process.env) as any; +process.env = mysqlConfigSchema.validateSync(process.env) as any; const env = mysqlConfig(); console.log('env: ', env); diff --git a/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts b/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts index 8444146a7..9a6fce4e4 100644 --- a/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts +++ b/apps/api/src/configs/modules/typeorm-config/typeorm-config.service.ts @@ -13,14 +13,17 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { join } from 'path'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm'; +import type { + TypeOrmModuleOptions, + TypeOrmOptionsFactory, +} from '@nestjs/typeorm'; import * as dotenv from 'dotenv'; -import { join } from 'path'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; -import { ConfigServiceType } from '@/types/config-service.type'; +import type { ConfigServiceType } from '@/types/config-service.type'; dotenv.config(); @Injectable() diff --git a/apps/api/src/configs/mysql.config.ts b/apps/api/src/configs/mysql.config.ts index a57d401db..1862fa6ca 100644 --- a/apps/api/src/configs/mysql.config.ts +++ b/apps/api/src/configs/mysql.config.ts @@ -16,15 +16,9 @@ import { registerAs } from '@nestjs/config'; import * as yup from 'yup'; -export const mySqlConfigSchema = yup.object({ - MYSQL_PRIMARY_URL: yup - .string() - .default('mysql://userfeedback:userfeedback@localhost:13306/userfeedback'), - MYSQL_SECONDARY_URLS: yup - .string() - .default( - '["mysql://userfeedback:userfeedback@localhost:13306/userfeedback"]', - ), +export const mysqlConfigSchema = yup.object({ + MYSQL_PRIMARY_URL: yup.string().required(), + MYSQL_SECONDARY_URLS: yup.array().required(), }); export const mysqlConfig = registerAs('mysql', () => ({ diff --git a/apps/api/src/domains/auth/auth.controller.spec.ts b/apps/api/src/domains/auth/auth.controller.spec.ts index 99305ea2b..22f3ecdbf 100644 --- a/apps/api/src/domains/auth/auth.controller.spec.ts +++ b/apps/api/src/domains/auth/auth.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import dayjs from 'dayjs'; -import { getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider } from '@/test-utils/util-functions'; import { TenantService } from '../tenant/tenant.service'; import { UserDto } from '../user/dtos'; import { AuthController } from './auth.controller'; @@ -71,7 +70,7 @@ describe('AuthController', () => { }); it('verifyEmailCode', () => { const dto = new EmailVerificationCodeRequestDto(); - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); dto.email = faker.internet.email(); authController.verifyEmailCode(dto); @@ -86,7 +85,7 @@ describe('AuthController', () => { }); it('signUpInvitationUser', () => { const dto = new InvitationUserSignUpRequestDto(); - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); dto.email = faker.internet.email(); dto.password = faker.internet.password(); authController.signUpInvitationUser(dto); diff --git a/apps/api/src/domains/auth/auth.controller.ts b/apps/api/src/domains/auth/auth.controller.ts index fb8e6ccf7..078377a14 100644 --- a/apps/api/src/domains/auth/auth.controller.ts +++ b/apps/api/src/domains/auth/auth.controller.ts @@ -28,6 +28,7 @@ import { ApiBody, ApiCreatedResponse, ApiOkResponse, + ApiQuery, ApiTags, } from '@nestjs/swagger'; @@ -42,8 +43,11 @@ import { InvitationUserSignUpRequestDto, OAuthUserSignUpRequestDto, } from './dtos/requests'; -import { OAuthLoginUrlResponseDto, SignInResponseDto } from './dtos/responses'; -import { SendEmailCodeResponseDto } from './dtos/responses'; +import { + OAuthLoginUrlResponseDto, + SendEmailCodeResponseDto, + SignInResponseDto, +} from './dtos/responses'; import { JwtAuthGuard } from './guards'; import { UseEmailGuard } from './guards/use-email.guard'; import { UseOAuthGuard } from './guards/use-oauth.guard'; @@ -95,10 +99,11 @@ export class AuthController { } @UseGuards(UseOAuthGuard) + @ApiQuery({ name: 'callback_url', required: false }) @ApiOkResponse({ type: OAuthLoginUrlResponseDto }) @Get('signIn/oauth/loginURL') - async redirectToLoginURL() { - return { url: await this.authService.getOAuthLoginURL() }; + async redirectToLoginURL(@Query('callback_url') callbackUrl: string) { + return { url: await this.authService.getOAuthLoginURL(callbackUrl) }; } @UseGuards(UseOAuthGuard) diff --git a/apps/api/src/domains/auth/auth.module.ts b/apps/api/src/domains/auth/auth.module.ts index ee9e6ef0d..bbbfe7315 100644 --- a/apps/api/src/domains/auth/auth.module.ts +++ b/apps/api/src/domains/auth/auth.module.ts @@ -20,8 +20,7 @@ import { PassportModule } from '@nestjs/passport'; import { CodeModule } from '@/shared/code/code.module'; import { MailingModule } from '@/shared/mailing/mailing.module'; -import { ConfigServiceType } from '@/types/config-service.type'; - +import type { ConfigServiceType } from '@/types/config-service.type'; import { ApiKeyModule } from '../project/api-key/api-key.module'; import { MemberModule } from '../project/member/member.module'; import { RoleModule } from '../project/role/role.module'; diff --git a/apps/api/src/domains/auth/auth.service.spec.ts b/apps/api/src/domains/auth/auth.service.spec.ts index 127d88667..f7fbb4d84 100644 --- a/apps/api/src/domains/auth/auth.service.spec.ts +++ b/apps/api/src/domains/auth/auth.service.spec.ts @@ -15,26 +15,20 @@ */ import { faker } from '@faker-js/faker'; import { BadRequestException } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import * as bcrypt from 'bcrypt'; -import { ClsService } from 'nestjs-cls'; -import { Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { CodeEntity } from '@/shared/code/code.entity'; -import { CodeServiceProviders } from '@/shared/code/code.service.spec'; -import { EmailVerificationMailingService } from '@/shared/mailing/email-verification-mailing.service'; import { NotVerifiedEmailException } from '@/shared/mailing/exceptions'; -import { getMockProvider } from '@/utils/test-utils'; - +import { + AuthServiceProviders, + MockEmailVerificationMailingService, + MockJwtService, +} from '../../test-utils/providers/auth.service.providers'; import { ApiKeyEntity } from '../project/api-key/api-key.entity'; -import { ApiKeyServiceProviders } from '../project/api-key/api-key.service.spec'; -import { MemberServiceProviders } from '../project/member/member.service.spec'; -import { RoleServiceProviders } from '../project/role/role.service.spec'; import { TenantEntity } from '../tenant/tenant.entity'; -import { TenantServiceProviders } from '../tenant/tenant.service.spec'; -import { CreateUserServiceProviders } from '../user/create-user.service.spec'; import { UserDto } from '../user/dtos'; import { UserStateEnum } from '../user/entities/enums'; import { UserEntity } from '../user/entities/user.entity'; @@ -42,7 +36,6 @@ import { UserAlreadyExistsException, UserNotFoundException, } from '../user/exceptions'; -import { UserServiceProviders } from '../user/user.service.spec'; import { AuthService } from './auth.service'; import { SendEmailCodeDto, @@ -51,29 +44,6 @@ import { } from './dtos'; import { PasswordNotMatchException, UserBlockedException } from './exceptions'; -const MockJwtService = { - sign: jest.fn(), -}; -const MockEmailVerificationMailingService = { - send: jest.fn(), -}; -const AuthServiceProviders = [ - AuthService, - ...CreateUserServiceProviders, - ...UserServiceProviders, - getMockProvider(JwtService, MockJwtService), - getMockProvider( - EmailVerificationMailingService, - MockEmailVerificationMailingService, - ), - ...CodeServiceProviders, - ...ApiKeyServiceProviders, - ...TenantServiceProviders, - ...RoleServiceProviders, - ...MemberServiceProviders, - ClsService, -]; - describe('auth service ', () => { let authService: AuthService; let userRepo: Repository; @@ -143,7 +113,6 @@ describe('auth service ', () => { }); }); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('verifyEmailCode', () => {}); describe('validateEmailUser', () => { @@ -267,10 +236,8 @@ describe('auth service ', () => { }); }); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('signUpInvitationUser', () => {}); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('signUpOAuthUser', () => {}); describe('signIn', () => { @@ -280,7 +247,7 @@ describe('auth service ', () => { jest.spyOn(userRepo, 'findOne').mockResolvedValue(user); const dto = new UserDto(); dto.email = faker.internet.email(); - dto.id = faker.datatype.number(); + dto.id = faker.number.int(); await authService.signIn(dto); @@ -292,7 +259,7 @@ describe('auth service ', () => { jest.spyOn(userRepo, 'findOne').mockResolvedValue(user); const dto = new UserDto(); dto.email = faker.internet.email(); - dto.id = faker.datatype.number(); + dto.id = faker.number.int(); await expect(authService.signIn(dto)).rejects.toThrow( UserBlockedException, @@ -302,13 +269,12 @@ describe('auth service ', () => { }); }); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('refreshToken', () => {}); describe('validateApiKey', () => { it('validating an api key succeeds with a valid api key', async () => { - const apiKey = faker.datatype.uuid(); - const projectId = faker.datatype.number(); + const apiKey = faker.string.uuid(); + const projectId = faker.number.int(); jest.spyOn(apiKeyRepo, 'find').mockResolvedValue([{}] as ApiKeyEntity[]); const result = await authService.validateApiKey(apiKey, projectId); @@ -317,8 +283,8 @@ describe('auth service ', () => { expect(result).toEqual(true); }); it('validating an api key succeeds with an invalid api key', async () => { - const apiKey = faker.datatype.uuid(); - const projectId = faker.datatype.number(); + const apiKey = faker.string.uuid(); + const projectId = faker.number.int(); jest.spyOn(apiKeyRepo, 'find').mockResolvedValue([] as ApiKeyEntity[]); const result = await authService.validateApiKey(apiKey, projectId); @@ -330,8 +296,8 @@ describe('auth service ', () => { describe('getOAuthLoginURL', () => { it('getting an oauth login url succeeds with oauth using tenant', async () => { - const clientId = faker.datatype.string(); - const scopeString = faker.datatype.string(); + const clientId = faker.string.sample(); + const scopeString = faker.string.sample(); const authCodeRequestURL = faker.internet.domainName(); jest.spyOn(tenantRepo, 'find').mockResolvedValue([ { @@ -379,6 +345,5 @@ describe('auth service ', () => { }); }); - // eslint-disable-next-line @typescript-eslint/no-empty-function describe('signInByOAuth', () => {}); }); diff --git a/apps/api/src/domains/auth/auth.service.ts b/apps/api/src/domains/auth/auth.service.ts index 91fb9b627..bfac6f6da 100644 --- a/apps/api/src/domains/auth/auth.service.ts +++ b/apps/api/src/domains/auth/auth.service.ts @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ +import crypto from 'crypto'; import { BadRequestException, Injectable, @@ -20,17 +21,13 @@ import { Logger, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; import * as bcrypt from 'bcrypt'; -import crypto from 'crypto'; import dayjs from 'dayjs'; -import { ClsService } from 'nestjs-cls'; import { Transactional } from 'typeorm-transactional'; import { EmailVerificationMailingService } from '@/shared/mailing/email-verification-mailing.service'; import { NotVerifiedEmailException } from '@/shared/mailing/exceptions'; -import { ClsServiceType } from '@/types/cls-service.type'; - import { CodeTypeEnum } from '../../shared/code/code-type.enum'; import { CodeService } from '../../shared/code/code.service'; import { ApiKeyService } from '../project/api-key/api-key.service'; @@ -45,14 +42,16 @@ import { UserNotFoundException, } from '../user/exceptions'; import { UserService } from '../user/user.service'; -import { +import type { JwtDto, SendEmailCodeDto, + ValidateEmailUserDto, + VerifyEmailCodeDto, +} from './dtos'; +import { SignUpEmailUserDto, SignUpInvitationUserDto, SignUpOauthUserDto, - ValidateEmailUserDto, - VerifyEmailCodeDto, } from './dtos'; import { PasswordNotMatchException, UserBlockedException } from './exceptions'; @@ -60,8 +59,7 @@ import { PasswordNotMatchException, UserBlockedException } from './exceptions'; export class AuthService { private logger = new Logger(AuthService.name); private REDIRECT_URI = `${process.env.BASE_URL}/auth/oauth-callback`; - // private REDIRECT_URI = - // 'https://demaecan-account.line-apps-beta.com/auth/support/test/authorize-success'; + constructor( private readonly createUserService: CreateUserService, private readonly userService: UserService, @@ -72,7 +70,6 @@ export class AuthService { private readonly tenantService: TenantService, private readonly roleService: RoleService, private readonly memberService: MemberService, - private readonly cls: ClsService, ) {} async sendEmailCode({ email }: SendEmailCodeDto) { @@ -203,7 +200,7 @@ export class AuthService { return false; } - async getOAuthLoginURL() { + async getOAuthLoginURL(callback_url?: string) { const { useOAuth, oauthConfig } = await this.tenantService.findOne(); if (!useOAuth) { @@ -219,6 +216,7 @@ export class AuthService { response_type: 'code', state: crypto.randomBytes(10).toString('hex'), scope: oauthConfig.scopeString, + callback_url: encodeURIComponent(callback_url), }); return `${oauthConfig.authCodeRequestURL}?${params}`; @@ -235,7 +233,6 @@ export class AuthService { } const { accessTokenRequestURL, clientId, clientSecret } = oauthConfig; - try { const { data } = await axios.post( accessTokenRequestURL, @@ -253,10 +250,17 @@ export class AuthService { }, }, ); - return data.access_token; - } catch (e) { - this.logger.error(e); + } catch (error) { + if (error instanceof AxiosError) { + throw new InternalServerErrorException({ + axiosError: { + ...error.response.data, + status: error.response.status, + }, + }); + } + throw error; } } @@ -266,17 +270,27 @@ export class AuthService { if (!oauthConfig) { throw new BadRequestException('OAuth Config is required.'); } - - const { data } = await axios.get(oauthConfig.userProfileRequestURL, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - return data[oauthConfig.emailKey]; + try { + const { data } = await axios.get(oauthConfig.userProfileRequestURL, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + return data[oauthConfig.emailKey]; + } catch (error) { + if (error instanceof AxiosError) { + throw new InternalServerErrorException({ + axiosError: { + ...error.response.data, + status: error.response.status, + }, + }); + } + throw error; + } } async signInByOAuth(code: string) { const accessToken = await this.getAccessToken(code); + const email = await this.getEmailByAccessToken(accessToken); const user = await this.userService.findByEmailAndSignUpMethod( diff --git a/apps/api/src/domains/auth/guards/api-key.guard.ts b/apps/api/src/domains/auth/guards/api-key.guard.ts index 3bbb6fc72..ce191d65c 100644 --- a/apps/api/src/domains/auth/guards/api-key.guard.ts +++ b/apps/api/src/domains/auth/guards/api-key.guard.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import type { CanActivate, ExecutionContext } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { AuthService } from '../auth.service'; diff --git a/apps/api/src/domains/auth/guards/jwt-auth.guard.ts b/apps/api/src/domains/auth/guards/jwt-auth.guard.ts index 24e40aed7..d9f351788 100644 --- a/apps/api/src/domains/auth/guards/jwt-auth.guard.ts +++ b/apps/api/src/domains/auth/guards/jwt-auth.guard.ts @@ -13,16 +13,13 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { - ExecutionContext, - Injectable, - UnauthorizedException, -} from '@nestjs/common'; +import type { ExecutionContext } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ClsService } from 'nestjs-cls'; -import { Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; -import { ClsServiceType } from '@/types/cls-service.type'; +import type { ClsServiceType } from '@/types/cls-service.type'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { diff --git a/apps/api/src/domains/auth/guards/use-email.guard.ts b/apps/api/src/domains/auth/guards/use-email.guard.ts index 24ee66269..2e7b72e95 100644 --- a/apps/api/src/domains/auth/guards/use-email.guard.ts +++ b/apps/api/src/domains/auth/guards/use-email.guard.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { CanActivate, Injectable } from '@nestjs/common'; +import type { CanActivate } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { TenantService } from '@/domains/tenant/tenant.service'; diff --git a/apps/api/src/domains/auth/guards/use-oauth.guard.ts b/apps/api/src/domains/auth/guards/use-oauth.guard.ts index 1bc3d9121..dbafadfdc 100644 --- a/apps/api/src/domains/auth/guards/use-oauth.guard.ts +++ b/apps/api/src/domains/auth/guards/use-oauth.guard.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { CanActivate, Injectable } from '@nestjs/common'; +import type { CanActivate } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { TenantService } from '@/domains/tenant/tenant.service'; diff --git a/apps/api/src/domains/auth/strategies/jwt.strategy.ts b/apps/api/src/domains/auth/strategies/jwt.strategy.ts index 0a66691c2..f92ba8396 100644 --- a/apps/api/src/domains/auth/strategies/jwt.strategy.ts +++ b/apps/api/src/domains/auth/strategies/jwt.strategy.ts @@ -18,8 +18,8 @@ import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { UserTypeEnum } from '@/domains/user/entities/enums'; -import { ConfigServiceType } from '@/types/config-service.type'; +import type { UserTypeEnum } from '@/domains/user/entities/enums'; +import type { ConfigServiceType } from '@/types/config-service.type'; interface IPayload { sub: string; diff --git a/apps/api/src/domains/auth/strategies/local.strategy.ts b/apps/api/src/domains/auth/strategies/local.strategy.ts index 20fd1b5c1..38505ea32 100644 --- a/apps/api/src/domains/auth/strategies/local.strategy.ts +++ b/apps/api/src/domains/auth/strategies/local.strategy.ts @@ -18,7 +18,6 @@ import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; import { UserDto } from '@/domains/user/dtos'; - import { AuthService } from '../auth.service'; @Injectable() diff --git a/apps/api/src/domains/channel/channel/channel.controller.spec.ts b/apps/api/src/domains/channel/channel/channel.controller.spec.ts index 65c4c018e..e48165d8b 100644 --- a/apps/api/src/domains/channel/channel/channel.controller.spec.ts +++ b/apps/api/src/domains/channel/channel/channel.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { DataSource } from 'typeorm'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { ChannelController } from './channel.controller'; import { ChannelService } from './channel.service'; import { @@ -51,10 +50,10 @@ describe('ChannelController', () => { it('should return an array of users', async () => { jest.spyOn(MockChannelService, 'create'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const dto = new CreateChannelRequestDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); dto.fields = []; await channelController.create(projectId, dto); @@ -65,10 +64,10 @@ describe('ChannelController', () => { it('should return an array of users', async () => { jest.spyOn(MockChannelService, 'findAllByProjectId'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const dto = new FindChannelsByProjectIdRequestDto(); - dto.limit = faker.datatype.number(); - dto.page = faker.datatype.number(); + dto.limit = faker.number.int(); + dto.page = faker.number.int(); await channelController.findAllByProjectId(projectId, dto); expect(MockChannelService.findAllByProjectId).toBeCalledTimes(1); @@ -77,7 +76,7 @@ describe('ChannelController', () => { describe('delete', () => { it('', async () => { jest.spyOn(MockChannelService, 'deleteById'); - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); await channelController.delete(channelId); expect(MockChannelService.deleteById).toBeCalledTimes(1); diff --git a/apps/api/src/domains/channel/channel/channel.controller.ts b/apps/api/src/domains/channel/channel/channel.controller.ts index 7ddb73c55..3dce7ca6e 100644 --- a/apps/api/src/domains/channel/channel/channel.controller.ts +++ b/apps/api/src/domains/channel/channel/channel.controller.ts @@ -33,7 +33,6 @@ import { import { PermissionEnum } from '@/domains/project/role/permission.enum'; import { RequirePermission } from '@/domains/project/role/require-permission.decorator'; - import { ChannelService } from './channel.service'; import { CreateChannelDto } from './dtos'; import { diff --git a/apps/api/src/domains/channel/channel/channel.entity.ts b/apps/api/src/domains/channel/channel/channel.entity.ts index a283fa3f5..7210927fc 100644 --- a/apps/api/src/domains/channel/channel/channel.entity.ts +++ b/apps/api/src/domains/channel/channel/channel.entity.ts @@ -24,7 +24,6 @@ import { } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { FeedbackEntity } from '../../feedback/feedback.entity'; import { ProjectEntity } from '../../project/project/project.entity'; import { FieldEntity } from '../field/field.entity'; diff --git a/apps/api/src/domains/channel/channel/channel.module.ts b/apps/api/src/domains/channel/channel/channel.module.ts index dca4143af..210d1dc04 100644 --- a/apps/api/src/domains/channel/channel/channel.module.ts +++ b/apps/api/src/domains/channel/channel/channel.module.ts @@ -13,14 +13,13 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Module, forwardRef } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpensearchRepository } from '@/common/repositories'; import { FeedbackModule } from '@/domains/feedback/feedback.module'; import { ProjectEntity } from '@/domains/project/project/project.entity'; import { ProjectModule } from '@/domains/project/project/project.module'; - import { FieldModule } from '../field/field.module'; import { ChannelController } from './channel.controller'; import { ChannelEntity } from './channel.entity'; diff --git a/apps/api/src/domains/channel/channel/channel.mysql.service.ts b/apps/api/src/domains/channel/channel/channel.mysql.service.ts index 8a3379836..8bdf15558 100644 --- a/apps/api/src/domains/channel/channel/channel.mysql.service.ts +++ b/apps/api/src/domains/channel/channel/channel.mysql.service.ts @@ -20,14 +20,9 @@ import { Like, Not, Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; import { isSelectFieldFormat } from '@/common/enums'; - import { ChannelEntity } from './channel.entity'; -import { - CreateChannelDto, - FindAllChannelsByProjectIdDto, - FindByChannelIdDto, - UpdateChannelDto, -} from './dtos'; +import type { FindAllChannelsByProjectIdDto, FindByChannelIdDto } from './dtos'; +import { CreateChannelDto, UpdateChannelDto } from './dtos'; import { ChannelAlreadyExistsException, ChannelInvalidNameException, diff --git a/apps/api/src/domains/channel/channel/channel.service.spec.ts b/apps/api/src/domains/channel/channel/channel.service.spec.ts index bc9124aa8..2548599a8 100644 --- a/apps/api/src/domains/channel/channel/channel.service.spec.ts +++ b/apps/api/src/domains/channel/channel/channel.service.spec.ts @@ -16,21 +16,14 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { OpensearchRepository } from '@/common/repositories'; -import { ProjectServiceProviders } from '@/domains/project/project/project.service.spec'; -import { createFieldDto } from '@/utils/test-util-fixture'; -import { - MockOpensearchRepository, - getMockProvider, - mockRepository, -} from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { createFieldDto } from '@/test-utils/fixtures'; +import { MockOpensearchRepository } from '@/test-utils/util-functions'; +import { ChannelServiceProviders } from '../../../test-utils/providers/channel.service.providers'; import { ChannelEntity } from '../../channel/channel/channel.entity'; import { ProjectEntity } from '../../project/project/project.entity'; import { FieldEntity } from '../field/field.entity'; -import { FieldServiceProviders } from '../field/field.service.spec'; import { ChannelMySQLService } from './channel.mysql.service'; import { ChannelService } from './channel.service'; import { CreateChannelDto, FindByChannelIdDto, UpdateChannelDto } from './dtos'; @@ -40,24 +33,12 @@ import { ChannelNotFoundException, } from './exceptions'; -export const ChannelServiceProviders = [ - ChannelService, - ChannelMySQLService, - { - provide: getRepositoryToken(ChannelEntity), - useValue: mockRepository(), - }, - getMockProvider(OpensearchRepository, MockOpensearchRepository), - ...ProjectServiceProviders, - ...FieldServiceProviders, -]; - const channelFixture = new ChannelEntity(); -channelFixture.id = faker.datatype.number(); -channelFixture.name = faker.datatype.string(); -channelFixture.description = faker.datatype.string(); +channelFixture.id = faker.number.int(); +channelFixture.name = faker.string.sample(); +channelFixture.description = faker.string.sample(); channelFixture.project = new ProjectEntity(); -channelFixture.project.id = faker.datatype.number(); +channelFixture.project.id = faker.number.int(); channelFixture.fields = []; describe('ChannelService', () => { @@ -79,7 +60,7 @@ describe('ChannelService', () => { describe('create', () => { it('creating a channel succeeds with valid inputs', async () => { - const fieldCount = faker.datatype.number({ min: 1, max: 10 }); + const fieldCount = faker.number.int({ min: 1, max: 10 }); const dto = new CreateChannelDto(); dto.name = channelFixture.name; dto.description = channelFixture.description; @@ -92,7 +73,7 @@ describe('ChannelService', () => { jest.spyOn(channelRepo, 'save').mockResolvedValue(channelFixture); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); const channel = await channelService.create(dto); @@ -107,11 +88,11 @@ describe('ChannelService', () => { }); }); it('creating a channel fails with a duplicate name', async () => { - const fieldCount = faker.datatype.number({ min: 1, max: 10 }); + const fieldCount = faker.number.int({ min: 1, max: 10 }); const dto = new CreateChannelDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); - dto.projectId = faker.datatype.number(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); + dto.projectId = faker.number.int(); dto.fields = Array.from({ length: fieldCount }).map(createFieldDto); jest .spyOn(projectRepo, 'findOneBy') @@ -140,7 +121,7 @@ describe('ChannelService', () => { }); it('finding by an id fails with a nonexistent id', async () => { const dto = new FindByChannelIdDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); jest .spyOn(channelRepo, 'findOne') .mockResolvedValue(null as ChannelEntity); @@ -155,8 +136,8 @@ describe('ChannelService', () => { it('updating succeeds with valid inputs', async () => { const channelId = channelFixture.id; const dto = new UpdateChannelDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); jest .spyOn(ChannelMySQLService.prototype, 'findById') .mockResolvedValue(channelFixture); @@ -174,13 +155,13 @@ describe('ChannelService', () => { const channelId = channelFixture.id; const dto = new UpdateChannelDto(); dto.name = channelFixture.name; - dto.description = faker.datatype.string(); + dto.description = faker.string.sample(); jest .spyOn(ChannelMySQLService.prototype, 'findById') .mockResolvedValue(channelFixture); jest.spyOn(channelRepo, 'findOne').mockResolvedValue({ ...channelFixture, - id: faker.datatype.number(), + id: faker.number.int(), } as ChannelEntity); await expect(channelService.updateInfo(channelId, dto)).rejects.toThrow( @@ -191,7 +172,7 @@ describe('ChannelService', () => { describe('deleteById', () => { it('deleting by an id succeeds with a valid id', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const channel = new ChannelEntity(); channel.id = channelId; diff --git a/apps/api/src/domains/channel/channel/channel.service.ts b/apps/api/src/domains/channel/channel/channel.service.ts index 0ac25d7f2..442d0c1e7 100644 --- a/apps/api/src/domains/channel/channel/channel.service.ts +++ b/apps/api/src/domains/channel/channel/channel.service.ts @@ -19,13 +19,11 @@ import { Transactional } from 'typeorm-transactional'; import { OpensearchRepository } from '@/common/repositories'; import { OS_USE } from '@/configs/opensearch.config'; import { ProjectService } from '@/domains/project/project/project.service'; - import { FieldService } from '../field/field.service'; import { ChannelMySQLService } from './channel.mysql.service'; +import type { FindAllChannelsByProjectIdDto, FindByChannelIdDto } from './dtos'; import { CreateChannelDto, - FindAllChannelsByProjectIdDto, - FindByChannelIdDto, UpdateChannelDto, UpdateChannelFieldsDto, } from './dtos'; @@ -55,11 +53,11 @@ export class ChannelService { } async findAllByProjectId(dto: FindAllChannelsByProjectIdDto) { - return this.channelMySQLService.findAllByProjectId(dto); + return await this.channelMySQLService.findAllByProjectId(dto); } async findById(dto: FindByChannelIdDto) { - return this.channelMySQLService.findById(dto); + return await this.channelMySQLService.findById(dto); } @Transactional() diff --git a/apps/api/src/domains/channel/channel/create-channel.dto.ts b/apps/api/src/domains/channel/channel/create-channel.dto.ts index cf9abe31a..fcaab3a0c 100644 --- a/apps/api/src/domains/channel/channel/create-channel.dto.ts +++ b/apps/api/src/domains/channel/channel/create-channel.dto.ts @@ -13,10 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { ChannelEntity } from '@/domains/channel/channel/channel.entity'; - import { CreateFieldDto } from '../field/dtos'; export class CreateChannelDto { diff --git a/apps/api/src/domains/channel/channel/dtos/create-channel.dto.ts b/apps/api/src/domains/channel/channel/dtos/create-channel.dto.ts index 2eb508d17..1677bcefd 100644 --- a/apps/api/src/domains/channel/channel/dtos/create-channel.dto.ts +++ b/apps/api/src/domains/channel/channel/dtos/create-channel.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { CreateFieldDto } from '../../field/dtos/create-field.dto'; import { ChannelEntity } from '../channel.entity'; diff --git a/apps/api/src/domains/channel/channel/dtos/find-all-channels-by-project-id.dto.ts b/apps/api/src/domains/channel/channel/dtos/find-all-channels-by-project-id.dto.ts index d3939f22f..eeaa3d9fa 100644 --- a/apps/api/src/domains/channel/channel/dtos/find-all-channels-by-project-id.dto.ts +++ b/apps/api/src/domains/channel/channel/dtos/find-all-channels-by-project-id.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { IPaginationOptions } from 'nestjs-typeorm-paginate'; +import type { IPaginationOptions } from 'nestjs-typeorm-paginate'; export class FindAllChannelsByProjectIdDto { options: IPaginationOptions; diff --git a/apps/api/src/domains/channel/channel/dtos/responses/find-channel-by-id-response.dto.ts b/apps/api/src/domains/channel/channel/dtos/responses/find-channel-by-id-response.dto.ts index 7966d1cb5..12f48ecff 100644 --- a/apps/api/src/domains/channel/channel/dtos/responses/find-channel-by-id-response.dto.ts +++ b/apps/api/src/domains/channel/channel/dtos/responses/find-channel-by-id-response.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { FindFieldsResponseDto } from '@/domains/channel/field/dtos/responses'; diff --git a/apps/api/src/domains/channel/channel/dtos/responses/find-channels-by-id-response.dto.ts b/apps/api/src/domains/channel/channel/dtos/responses/find-channels-by-id-response.dto.ts index af2fce41a..e55f36dbb 100644 --- a/apps/api/src/domains/channel/channel/dtos/responses/find-channels-by-id-response.dto.ts +++ b/apps/api/src/domains/channel/channel/dtos/responses/find-channels-by-id-response.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PaginationResponseDto } from '@/common/dtos'; diff --git a/apps/api/src/domains/channel/field/dtos/create-many-fields.dto.ts b/apps/api/src/domains/channel/field/dtos/create-many-fields.dto.ts index 76d42938d..78f11954d 100644 --- a/apps/api/src/domains/channel/field/dtos/create-many-fields.dto.ts +++ b/apps/api/src/domains/channel/field/dtos/create-many-fields.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { CreateFieldDto } from './create-field.dto'; +import type { CreateFieldDto } from './create-field.dto'; export class CreateManyFieldsDto { channelId: number; diff --git a/apps/api/src/domains/channel/field/dtos/replace-many-fields.dto.ts b/apps/api/src/domains/channel/field/dtos/replace-many-fields.dto.ts index d7efe0d1a..a85527e67 100644 --- a/apps/api/src/domains/channel/field/dtos/replace-many-fields.dto.ts +++ b/apps/api/src/domains/channel/field/dtos/replace-many-fields.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { ReplaceFieldDto } from './replace-field.dto'; +import type { ReplaceFieldDto } from './replace-field.dto'; export class ReplaceManyFieldsDto { channelId: number; diff --git a/apps/api/src/domains/channel/field/dtos/responses/find-fields-response.dto.ts b/apps/api/src/domains/channel/field/dtos/responses/find-fields-response.dto.ts index 26701881c..3faf726eb 100644 --- a/apps/api/src/domains/channel/field/dtos/responses/find-fields-response.dto.ts +++ b/apps/api/src/domains/channel/field/dtos/responses/find-fields-response.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { FieldFormatEnum, diff --git a/apps/api/src/domains/channel/field/field.entity.ts b/apps/api/src/domains/channel/field/field.entity.ts index 3eb0b912e..c987df06f 100644 --- a/apps/api/src/domains/channel/field/field.entity.ts +++ b/apps/api/src/domains/channel/field/field.entity.ts @@ -24,7 +24,6 @@ import { } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { FieldFormatEnum, FieldStatusEnum, diff --git a/apps/api/src/domains/channel/field/field.module.ts b/apps/api/src/domains/channel/field/field.module.ts index 4d5ba95ed..7305fe38c 100644 --- a/apps/api/src/domains/channel/field/field.module.ts +++ b/apps/api/src/domains/channel/field/field.module.ts @@ -17,7 +17,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpensearchRepository } from '@/common/repositories'; - import { FieldEntity } from '../field/field.entity'; import { FieldMySQLService } from '../field/field.mysql.service'; import { FieldService } from '../field/field.service'; diff --git a/apps/api/src/domains/channel/field/field.mysql.service.ts b/apps/api/src/domains/channel/field/field.mysql.service.ts index 92a11ffce..89d830cae 100644 --- a/apps/api/src/domains/channel/field/field.mysql.service.ts +++ b/apps/api/src/domains/channel/field/field.mysql.service.ts @@ -25,15 +25,10 @@ import { isSelectFieldFormat, } from '@/common/enums'; import { validateUnique } from '@/utils/validate-unique'; - import { FieldEntity } from '../../channel/field/field.entity'; import { OptionService } from '../option/option.service'; -import { - CreateFieldDto, - CreateManyFieldsDto, - ReplaceFieldDto, - ReplaceManyFieldsDto, -} from './dtos'; +import type { CreateFieldDto, ReplaceFieldDto } from './dtos'; +import { CreateManyFieldsDto, ReplaceManyFieldsDto } from './dtos'; import { FieldKeyDuplicatedException, FieldNameDuplicatedException, diff --git a/apps/api/src/domains/channel/field/field.service.spec.ts b/apps/api/src/domains/channel/field/field.service.spec.ts index 7a6a86006..76d56a81c 100644 --- a/apps/api/src/domains/channel/field/field.service.spec.ts +++ b/apps/api/src/domains/channel/field/field.service.spec.ts @@ -17,39 +17,21 @@ import { faker } from '@faker-js/faker'; import { BadRequestException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { FieldFormatEnum, isSelectFieldFormat } from '@/common/enums'; -import { OpensearchRepository } from '@/common/repositories'; -import { createFieldDto, updateFieldDto } from '@/utils/test-util-fixture'; -import { - MockOpensearchRepository, - getMockProvider, - mockRepository, -} from '@/utils/test-utils'; - +import { createFieldDto, updateFieldDto } from '@/test-utils/fixtures'; +import { MockOpensearchRepository } from '@/test-utils/util-functions'; +import { FieldServiceProviders } from '../../../test-utils/providers/field.service.providers'; import { FieldEntity } from '../../channel/field/field.entity'; import { OptionEntity } from '../option/option.entity'; -import { OptionServiceProviders } from '../option/option.service.spec'; import { CreateManyFieldsDto, ReplaceManyFieldsDto } from './dtos'; import { FieldKeyDuplicatedException, FieldNameDuplicatedException, } from './exceptions'; -import { FieldMySQLService } from './field.mysql.service'; import { FieldService } from './field.service'; -export const FieldServiceProviders = [ - FieldService, - FieldMySQLService, - getMockProvider(OpensearchRepository, MockOpensearchRepository), - { - provide: getRepositoryToken(FieldEntity), - useValue: mockRepository(), - }, - ...OptionServiceProviders, -]; - const countSelect = (prev, curr) => { return isSelectFieldFormat(curr.format) && curr.options.length > 0 ? prev + 1 @@ -73,15 +55,15 @@ describe('FieldService suite', () => { describe('createMany', () => { it('creating many fields succeeds with valid inputs', async () => { - const channelId = faker.datatype.number(); - const fieldCount = faker.datatype.number({ min: 1, max: 10 }); + const channelId = faker.number.int(); + const fieldCount = faker.number.int({ min: 1, max: 10 }); const dto = new CreateManyFieldsDto(); dto.channelId = channelId; dto.fields = Array.from({ length: fieldCount }).map(createFieldDto); const selectFieldCount = dto.fields.reduce(countSelect, 0); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'save'); await fieldService.createMany(dto); @@ -91,7 +73,7 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).toBeCalledTimes(1); }); it('creating many fields fails with duplicate names', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const dto = new CreateManyFieldsDto(); dto.channelId = channelId; dto.fields = Array.from({ length: 2 }).map(() => @@ -105,7 +87,7 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('creating many fields fails with duplicate keys', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const dto = new CreateManyFieldsDto(); dto.channelId = channelId; dto.fields = Array.from({ length: 2 }).map(() => @@ -119,14 +101,14 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('creating many fields fails with options in non-select format field', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const dto = new CreateManyFieldsDto(); dto.channelId = channelId; dto.fields = Array.from({ length: 1 }).map(() => createFieldDto({ format: FieldFormatEnum.text, options: [ - { key: faker.datatype.string(), name: faker.datatype.string() }, + { key: faker.string.sample(), name: faker.string.sample() }, ], }), ); @@ -140,12 +122,12 @@ describe('FieldService suite', () => { }); describe('replaceMany', () => { it('replacing many fields succeeds with valid inputs', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(updateFieldDto); const creatingFieldDtos = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(createFieldDto); const dto = new ReplaceManyFieldsDto(); dto.channelId = channelId; @@ -155,7 +137,7 @@ describe('FieldService suite', () => { .mockResolvedValue(updatingFieldDtos as FieldEntity[]); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await fieldService.replaceMany(dto); @@ -167,7 +149,7 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).toBeCalledTimes(1); }); it('replacing many fields fails with duplicate names', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ length: 2, }).map(() => updateFieldDto({ name: 'duplicateName' })); @@ -179,7 +161,7 @@ describe('FieldService suite', () => { .mockResolvedValue(updatingFieldDtos as FieldEntity[]); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await expect(fieldService.replaceMany(dto)).rejects.toThrow( @@ -191,7 +173,7 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('replacing many fields fails with duplicate keys', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ length: 2, }).map(() => updateFieldDto({ key: 'duplicateKey' })); @@ -203,7 +185,7 @@ describe('FieldService suite', () => { .mockResolvedValue(updatingFieldDtos as FieldEntity[]); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await expect(fieldService.replaceMany(dto)).rejects.toThrow( @@ -215,14 +197,14 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('replacing many fields fails with options in non-select format field', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ length: 1, }).map(() => updateFieldDto({ format: FieldFormatEnum.text, options: [ - { key: faker.datatype.string(), name: faker.datatype.string() }, + { key: faker.string.sample(), name: faker.string.sample() }, ], }), ); @@ -234,7 +216,7 @@ describe('FieldService suite', () => { .mockResolvedValue(updatingFieldDtos as FieldEntity[]); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await expect(fieldService.replaceMany(dto)).rejects.toThrow( @@ -246,12 +228,12 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('replacing many fields fails with a nonexistent field', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(updateFieldDto); const creatingFieldDtos = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(createFieldDto); const dto = new ReplaceManyFieldsDto(); dto.channelId = channelId; @@ -261,7 +243,7 @@ describe('FieldService suite', () => { .mockResolvedValue(updatingFieldDtos.splice(1) as FieldEntity[]); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await expect(fieldService.replaceMany(dto)).rejects.toThrow( @@ -273,9 +255,9 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('replacing many fields fails with a format change', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(() => updateFieldDto({ format: FieldFormatEnum.keyword })); const dto = new ReplaceManyFieldsDto(); dto.channelId = channelId; @@ -288,7 +270,7 @@ describe('FieldService suite', () => { ); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await expect(fieldService.replaceMany(dto)).rejects.toThrow( @@ -300,22 +282,22 @@ describe('FieldService suite', () => { expect(MockOpensearchRepository.putMappings).not.toBeCalled(); }); it('replacing many fields fails with a key change', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const updatingFieldDtos = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(updateFieldDto); const dto = new ReplaceManyFieldsDto(); dto.channelId = channelId; dto.fields = JSON.parse(JSON.stringify(updatingFieldDtos)); jest.spyOn(fieldRepo, 'findBy').mockResolvedValue( updatingFieldDtos.map((field) => { - field.key = faker.datatype.string(); + field.key = faker.string.sample(); return field; }) as FieldEntity[], ); jest .spyOn(fieldRepo, 'save') - .mockResolvedValue({ id: faker.datatype.number() } as FieldEntity); + .mockResolvedValue({ id: faker.number.int() } as FieldEntity); jest.spyOn(optionRepo, 'find').mockResolvedValue([]); await expect(fieldService.replaceMany(dto)).rejects.toThrow( diff --git a/apps/api/src/domains/channel/field/field.service.ts b/apps/api/src/domains/channel/field/field.service.ts index 645a59301..8af377798 100644 --- a/apps/api/src/domains/channel/field/field.service.ts +++ b/apps/api/src/domains/channel/field/field.service.ts @@ -19,8 +19,7 @@ import { Transactional } from 'typeorm-transactional'; import { FieldFormatEnum } from '@/common/enums'; import { OpensearchRepository } from '@/common/repositories'; import { OS_USE } from '@/configs/opensearch.config'; - -import { FieldEntity } from '../../channel/field/field.entity'; +import type { FieldEntity } from '../../channel/field/field.entity'; import { CreateManyFieldsDto, ReplaceManyFieldsDto } from './dtos'; import { FieldMySQLService } from './field.mysql.service'; diff --git a/apps/api/src/domains/channel/option/option.controller.spec.ts b/apps/api/src/domains/channel/option/option.controller.spec.ts index bac01f0e9..36348b35b 100644 --- a/apps/api/src/domains/channel/option/option.controller.spec.ts +++ b/apps/api/src/domains/channel/option/option.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { DataSource } from 'typeorm'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { CreateOptionRequestDto } from './dtos/requests'; import { OptionController } from './option.controller'; import { OptionEntity } from './option.entity'; @@ -49,14 +48,14 @@ describe('SelectOptionController', () => { jest .spyOn(MockSelectOptionService, 'findByFieldId') .mockReturnValue(options); - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); await optionController.getOptions(fieldId); expect(MockSelectOptionService.findByFieldId).toBeCalledTimes(1); }); it('creaetOption', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateOptionRequestDto(); - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); await optionController.createOption(fieldId, dto); expect(MockSelectOptionService.create).toBeCalledTimes(1); }); diff --git a/apps/api/src/domains/channel/option/option.controller.ts b/apps/api/src/domains/channel/option/option.controller.ts index fe71be921..b33984c0d 100644 --- a/apps/api/src/domains/channel/option/option.controller.ts +++ b/apps/api/src/domains/channel/option/option.controller.ts @@ -25,7 +25,6 @@ import { ApiCreatedResponse, ApiOkResponse } from '@nestjs/swagger'; import { PermissionEnum } from '@/domains/project/role/permission.enum'; import { RequirePermission } from '@/domains/project/role/require-permission.decorator'; - import { CreateOptionRequestDto } from './dtos/requests'; import { CreateOptionResponseDto, diff --git a/apps/api/src/domains/channel/option/option.entity.ts b/apps/api/src/domains/channel/option/option.entity.ts index fa7587e76..85f6977a4 100644 --- a/apps/api/src/domains/channel/option/option.entity.ts +++ b/apps/api/src/domains/channel/option/option.entity.ts @@ -16,7 +16,6 @@ import { Column, Entity, ManyToOne, Relation } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { FieldEntity } from '../field/field.entity'; @Entity('options') diff --git a/apps/api/src/domains/channel/option/option.service.spec.ts b/apps/api/src/domains/channel/option/option.service.spec.ts index 92c8606d4..40c5b9f89 100644 --- a/apps/api/src/domains/channel/option/option.service.spec.ts +++ b/apps/api/src/domains/channel/option/option.service.spec.ts @@ -16,10 +16,9 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { OptionServiceProviders } from '../../../test-utils/providers/option.service.providers'; import { OptionEntity } from '../../channel/option/option.entity'; import { CreateManyOptionsDto, @@ -32,14 +31,6 @@ import { } from './exceptions'; import { OptionService } from './option.service'; -export const OptionServiceProviders = [ - OptionService, - { - provide: getRepositoryToken(OptionEntity), - useValue: mockRepository(), - }, -]; - describe('Option Test suite', () => { let optionService: OptionService; let optionRepo: Repository; @@ -55,15 +46,15 @@ describe('Option Test suite', () => { describe('create', () => { it('creating an option succeeds with a new valid input', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateOptionDto(); dto.fieldId = fieldId; - dto.key = faker.datatype.string(); - dto.name = faker.datatype.string(); + dto.key = faker.string.sample(); + dto.name = faker.string.sample(); jest.spyOn(optionRepo, 'findBy').mockResolvedValue([ { - key: faker.datatype.string(), - name: faker.datatype.string(), + key: faker.string.sample(), + name: faker.string.sample(), }, ] as OptionEntity[]); jest.spyOn(optionRepo, 'save'); @@ -82,12 +73,12 @@ describe('Option Test suite', () => { }); }); it('creating an option succeeds with an inactive input', async () => { - const fieldId = faker.datatype.number(); - const optionId = faker.datatype.number(); + const fieldId = faker.number.int(); + const optionId = faker.number.int(); const dto = new CreateOptionDto(); dto.fieldId = fieldId; - dto.key = faker.datatype.string(); - dto.name = faker.datatype.string(); + dto.key = faker.string.sample(); + dto.name = faker.string.sample(); jest.spyOn(optionRepo, 'findBy').mockResolvedValue([ { id: optionId, @@ -112,14 +103,14 @@ describe('Option Test suite', () => { }); }); it('creating an option fais with a duplicate name', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateOptionDto(); dto.fieldId = fieldId; - dto.key = faker.datatype.string(); - dto.name = faker.datatype.string(); + dto.key = faker.string.sample(); + dto.name = faker.string.sample(); jest.spyOn(optionRepo, 'findBy').mockResolvedValue([ { - key: faker.datatype.string(), + key: faker.string.sample(), name: dto.name, }, ] as OptionEntity[]); @@ -136,15 +127,15 @@ describe('Option Test suite', () => { expect(optionRepo.save).not.toBeCalled(); }); it('creating an option fais with a duplicate key', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateOptionDto(); dto.fieldId = fieldId; - dto.key = faker.datatype.string(); - dto.name = faker.datatype.string(); + dto.key = faker.string.sample(); + dto.name = faker.string.sample(); jest.spyOn(optionRepo, 'findBy').mockResolvedValue([ { key: dto.key, - name: faker.datatype.string(), + name: faker.string.sample(), }, ] as OptionEntity[]); jest.spyOn(optionRepo, 'save'); @@ -163,14 +154,14 @@ describe('Option Test suite', () => { describe('createMany', () => { it('creating many options succeeds with valid inputs', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateManyOptionsDto(); dto.fieldId = fieldId; dto.options = Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(() => ({ - key: faker.datatype.string(), - name: faker.datatype.string(), + key: faker.string.sample(), + name: faker.string.sample(), })); jest.spyOn(optionRepo, 'save'); @@ -186,13 +177,13 @@ describe('Option Test suite', () => { ); }); it('creating many options fails with duplicate names', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateManyOptionsDto(); dto.fieldId = fieldId; dto.options = Array.from({ - length: faker.datatype.number({ min: 2, max: 10 }), + length: faker.number.int({ min: 2, max: 10 }), }).map(() => ({ - key: faker.datatype.string(), + key: faker.string.sample(), name: 'duplicateName', })); jest.spyOn(optionRepo, 'save'); @@ -204,14 +195,14 @@ describe('Option Test suite', () => { expect(optionRepo.save).not.toBeCalled(); }); it('creating many options fails with duplicate keys', async () => { - const fieldId = faker.datatype.number(); + const fieldId = faker.number.int(); const dto = new CreateManyOptionsDto(); dto.fieldId = fieldId; dto.options = Array.from({ - length: faker.datatype.number({ min: 2, max: 10 }), + length: faker.number.int({ min: 2, max: 10 }), }).map(() => ({ key: 'duplicateKey', - name: faker.datatype.string(), + name: faker.string.sample(), })); jest.spyOn(optionRepo, 'save'); @@ -224,24 +215,24 @@ describe('Option Test suite', () => { }); describe('replaceMany', () => { it('replacing many options succeeds with valid inputs', async () => { - const fieldId = faker.datatype.number(); - const length = faker.datatype.number({ min: 1, max: 10 }); + const fieldId = faker.number.int(); + const length = faker.number.int({ min: 1, max: 10 }); const dto = new ReplaceManyOptionsDto(); dto.fieldId = fieldId; dto.options = Array.from({ length, }).map(() => ({ - id: faker.datatype.number(), - key: faker.datatype.string(), - name: faker.datatype.string(), + id: faker.number.int(), + key: faker.string.sample(), + name: faker.string.sample(), })); jest.spyOn(optionRepo, 'find').mockResolvedValue( Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(() => ({ - id: faker.datatype.number(), - key: faker.datatype.string(), - name: faker.datatype.string(), + id: faker.number.int(), + key: faker.string.sample(), + name: faker.string.sample(), deletedAt: null, })) as OptionEntity[], ); diff --git a/apps/api/src/domains/channel/option/option.service.ts b/apps/api/src/domains/channel/option/option.service.ts index 46c4031d1..550334119 100644 --- a/apps/api/src/domains/channel/option/option.service.ts +++ b/apps/api/src/domains/channel/option/option.service.ts @@ -19,7 +19,6 @@ import { Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; import { validateUnique } from '@/utils/validate-unique'; - import { OptionEntity } from '../../channel/option/option.entity'; import { CreateManyOptionsDto, diff --git a/apps/api/src/domains/feedback/dtos/create-feedback.dto.ts b/apps/api/src/domains/feedback/dtos/create-feedback.dto.ts index b389a5add..38138b865 100644 --- a/apps/api/src/domains/feedback/dtos/create-feedback.dto.ts +++ b/apps/api/src/domains/feedback/dtos/create-feedback.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { FeedbackEntity } from '@/domains/feedback/feedback.entity'; +import type { FeedbackEntity } from '@/domains/feedback/feedback.entity'; export class CreateFeedbackDto { channelId: number; diff --git a/apps/api/src/domains/feedback/dtos/find-feedbacks-by-channel-id.dto.ts b/apps/api/src/domains/feedback/dtos/find-feedbacks-by-channel-id.dto.ts index 68c200473..b47f54cad 100644 --- a/apps/api/src/domains/feedback/dtos/find-feedbacks-by-channel-id.dto.ts +++ b/apps/api/src/domains/feedback/dtos/find-feedbacks-by-channel-id.dto.ts @@ -13,9 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { PaginationDto, TimeRange } from '@/common/dtos'; -import { SortMethodEnum } from '@/common/enums'; -import { FieldEntity } from '@/domains/channel/field/field.entity'; +import type { TimeRange } from '@/common/dtos'; +import { PaginationDto } from '@/common/dtos'; +import type { SortMethodEnum } from '@/common/enums'; +import type { FieldEntity } from '@/domains/channel/field/field.entity'; export class FindFeedbacksByChannelIdDto extends PaginationDto { channelId: number; diff --git a/apps/api/src/domains/feedback/dtos/generate-excel.dto.ts b/apps/api/src/domains/feedback/dtos/generate-excel.dto.ts index 29945f06e..866bb7727 100644 --- a/apps/api/src/domains/feedback/dtos/generate-excel.dto.ts +++ b/apps/api/src/domains/feedback/dtos/generate-excel.dto.ts @@ -13,8 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { TimeRange } from '@/common/dtos'; -import { SortMethodEnum } from '@/common/enums'; +import type { TimeRange } from '@/common/dtos'; +import type { SortMethodEnum } from '@/common/enums'; export class GenerateExcelDto { channelId: number; diff --git a/apps/api/src/domains/feedback/dtos/os-query.dto.ts b/apps/api/src/domains/feedback/dtos/os-query.dto.ts index 35ef0cf04..b0da9798e 100644 --- a/apps/api/src/domains/feedback/dtos/os-query.dto.ts +++ b/apps/api/src/domains/feedback/dtos/os-query.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { TimeRange } from '@/common/dtos'; +import type { TimeRange } from '@/common/dtos'; export class OsQueryDto { bool: { diff --git a/apps/api/src/domains/feedback/dtos/requests/find-feedbacks-by-channel-id-request.dto.ts b/apps/api/src/domains/feedback/dtos/requests/find-feedbacks-by-channel-id-request.dto.ts index ebae8db28..0f0de2399 100644 --- a/apps/api/src/domains/feedback/dtos/requests/find-feedbacks-by-channel-id-request.dto.ts +++ b/apps/api/src/domains/feedback/dtos/requests/find-feedbacks-by-channel-id-request.dto.ts @@ -16,8 +16,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; -import { PaginationRequestDto, TimeRange } from '@/common/dtos'; -import { SortMethodEnum } from '@/common/enums'; +import type { TimeRange } from '@/common/dtos'; +import { PaginationRequestDto } from '@/common/dtos'; +import type { SortMethodEnum } from '@/common/enums'; export class FindFeedbacksByChannelIdRequestDto extends PaginationRequestDto { @ApiProperty({ required: false }) diff --git a/apps/api/src/domains/feedback/dtos/responses/find-feedbacks-by-channel-id-response.dto.ts b/apps/api/src/domains/feedback/dtos/responses/find-feedbacks-by-channel-id-response.dto.ts index 3acac8610..a5ab88d73 100644 --- a/apps/api/src/domains/feedback/dtos/responses/find-feedbacks-by-channel-id-response.dto.ts +++ b/apps/api/src/domains/feedback/dtos/responses/find-feedbacks-by-channel-id-response.dto.ts @@ -14,10 +14,10 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PaginationResponseDto } from '@/common/dtos'; -import { IssueEntity } from '@/domains/project/issue/issue.entity'; +import type { IssueEntity } from '@/domains/project/issue/issue.entity'; export class Feedback { [key: string]: any; diff --git a/apps/api/src/domains/feedback/dtos/scroll-feedbacks.dto.ts b/apps/api/src/domains/feedback/dtos/scroll-feedbacks.dto.ts index 75971c0d5..16225f0d5 100644 --- a/apps/api/src/domains/feedback/dtos/scroll-feedbacks.dto.ts +++ b/apps/api/src/domains/feedback/dtos/scroll-feedbacks.dto.ts @@ -13,9 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { TimeRange } from '@/common/dtos'; -import { SortMethodEnum } from '@/common/enums'; -import { FieldEntity } from '@/domains/channel/field/field.entity'; +import type { TimeRange } from '@/common/dtos'; +import type { SortMethodEnum } from '@/common/enums'; +import type { FieldEntity } from '@/domains/channel/field/field.entity'; export class ScrollFeedbacksDto { channelId: number; diff --git a/apps/api/src/domains/feedback/feedback.common.ts b/apps/api/src/domains/feedback/feedback.common.ts index 6c18133f2..c02d2c452 100644 --- a/apps/api/src/domains/feedback/feedback.common.ts +++ b/apps/api/src/domains/feedback/feedback.common.ts @@ -14,8 +14,7 @@ * under the License. */ import { FieldFormatEnum, SortMethodEnum } from '@/common/enums'; - -import { FieldEntity } from '../channel/field/field.entity'; +import type { FieldEntity } from '../channel/field/field.entity'; export function isInvalidSortMethod(method: SortMethodEnum) { return ![SortMethodEnum.ASC, SortMethodEnum.DESC].includes(method); diff --git a/apps/api/src/domains/feedback/feedback.controller.spec.ts b/apps/api/src/domains/feedback/feedback.controller.spec.ts index 8a91b9b4e..f4a043e20 100644 --- a/apps/api/src/domains/feedback/feedback.controller.spec.ts +++ b/apps/api/src/domains/feedback/feedback.controller.spec.ts @@ -15,13 +15,12 @@ */ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; -import { FastifyReply } from 'fastify'; +import type { FastifyReply } from 'fastify'; import { DataSource } from 'typeorm'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { AuthService } from '../auth/auth.service'; -import { ChannelEntity } from '../channel/channel/channel.entity'; +import type { ChannelEntity } from '../channel/channel/channel.entity'; import { ChannelService } from '../channel/channel/channel.service'; import { HistoryService } from '../history/history.service'; import { UserDto } from '../user/dtos'; @@ -70,11 +69,11 @@ describe('FeedbackController', () => { }); it('create', async () => { - const projectId = faker.datatype.number(); - const channelId = faker.datatype.number(); + const projectId = faker.number.int(); + const channelId = faker.number.int(); jest .spyOn(MockFeedbackService, 'create') - .mockResolvedValue({ id: faker.datatype.number() }); + .mockResolvedValue({ id: faker.number.int() }); jest.spyOn(MockChannelService, 'findById').mockResolvedValue({ project: { id: projectId }, } as ChannelEntity); @@ -83,11 +82,11 @@ describe('FeedbackController', () => { expect(MockFeedbackService.create).toBeCalledTimes(1); }); it('findByChannelId', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const dto = new FindFeedbacksByChannelIdRequestDto( - faker.datatype.number(), - faker.datatype.number(), + faker.number.int(), + faker.number.int(), {}, ); @@ -95,15 +94,15 @@ describe('FeedbackController', () => { expect(MockFeedbackService.findByChannelId).toBeCalledTimes(1); }); it('exportFeedbacks', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const response = { type: jest.fn(), header: jest.fn(), send: jest.fn(), } as unknown as FastifyReply; const dto = new ExportFeedbacksRequestDto( - faker.datatype.number(), - faker.datatype.number(), + faker.number.int(), + faker.number.int(), 'csv', ); const userDto = new UserDto(); @@ -112,7 +111,7 @@ describe('FeedbackController', () => { feedbackIds: [], }); jest.spyOn(MockChannelService, 'findById').mockResolvedValue({ - project: { name: faker.datatype.string() }, + project: { name: faker.string.sample() }, } as ChannelEntity); await feedbackController.exportFeedbacks( @@ -126,17 +125,17 @@ describe('FeedbackController', () => { expect(MockFeedbackService.generateFile).toBeCalledTimes(1); }); it('updateFeedback', async () => { - const channelId = faker.datatype.number(); - const feedbackId = faker.datatype.number(); - const body = { [faker.datatype.string()]: faker.datatype.string() }; + const channelId = faker.number.int(); + const feedbackId = faker.number.int(); + const body = { [faker.string.sample()]: faker.string.sample() }; await feedbackController.updateFeedback(channelId, feedbackId, body); expect(MockFeedbackService.updateFeedback).toBeCalledTimes(1); }); it('delete Feedback', async () => { - const channelId = faker.datatype.number(); - const feedbackIds = [faker.datatype.number()]; + const channelId = faker.number.int(); + const feedbackIds = [faker.number.int()]; const dto = new DeleteFeedbacksRequestDto(); dto.feedbackIds = feedbackIds; diff --git a/apps/api/src/domains/feedback/feedback.controller.ts b/apps/api/src/domains/feedback/feedback.controller.ts index fd37ef9c3..752efbb70 100644 --- a/apps/api/src/domains/feedback/feedback.controller.ts +++ b/apps/api/src/domains/feedback/feedback.controller.ts @@ -30,7 +30,6 @@ import dayjs from 'dayjs'; import { FastifyReply } from 'fastify'; import { ApiKeyAuthGuard } from '@/domains/auth/guards'; - import { ChannelService } from '../channel/channel/channel.service'; import { HistoryActionEnum } from '../history/history-action.enum'; import { EntityNameEnum } from '../history/history-entity.enum'; diff --git a/apps/api/src/domains/feedback/feedback.entity.ts b/apps/api/src/domains/feedback/feedback.entity.ts index 501b315dd..82c114fb6 100644 --- a/apps/api/src/domains/feedback/feedback.entity.ts +++ b/apps/api/src/domains/feedback/feedback.entity.ts @@ -24,7 +24,6 @@ import { } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { ChannelEntity } from '../channel/channel/channel.entity'; import { IssueEntity } from '../project/issue/issue.entity'; diff --git a/apps/api/src/domains/feedback/feedback.module.ts b/apps/api/src/domains/feedback/feedback.module.ts index 547eff2e3..f953b6bbe 100644 --- a/apps/api/src/domains/feedback/feedback.module.ts +++ b/apps/api/src/domains/feedback/feedback.module.ts @@ -13,11 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Module, forwardRef } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpensearchRepository } from '@/common/repositories'; - import { AuthModule } from '../auth/auth.module'; import { ChannelEntity } from '../channel/channel/channel.entity'; import { ChannelModule } from '../channel/channel/channel.module'; diff --git a/apps/api/src/domains/feedback/feedback.mysql.service.ts b/apps/api/src/domains/feedback/feedback.mysql.service.ts index 8d6688653..1b1ca379f 100644 --- a/apps/api/src/domains/feedback/feedback.mysql.service.ts +++ b/apps/api/src/domains/feedback/feedback.mysql.service.ts @@ -17,28 +17,29 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import dayjs from 'dayjs'; import { ClsService } from 'nestjs-cls'; -import { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; +import type { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; import { Brackets, QueryFailedError, Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; -import { TimeRange } from '@/common/dtos'; +import type { TimeRange } from '@/common/dtos'; import { FieldFormatEnum, FieldTypeEnum, SortMethodEnum } from '@/common/enums'; -import { ClsServiceType } from '@/types/cls-service.type'; - +import type { ClsServiceType } from '@/types/cls-service.type'; import { ChannelEntity } from '../channel/channel/channel.entity'; -import { FieldEntity } from '../channel/field/field.entity'; +import type { FieldEntity } from '../channel/field/field.entity'; import { OptionEntity } from '../channel/option/option.entity'; import { IssueEntity } from '../project/issue/issue.entity'; -import { - AddIssueDto, +import type { CountByProjectIdDto, - CreateFeedbackMySQLDto, DeleteByIdsDto, FindFeedbacksByChannelIdDto, +} from './dtos'; +import { + AddIssueDto, + CreateFeedbackMySQLDto, RemoveIssueDto, UpdateFeedbackMySQLDto, } from './dtos'; -import { Feedback } from './dtos/responses/find-feedbacks-by-channel-id-response.dto'; +import type { Feedback } from './dtos/responses/find-feedbacks-by-channel-id-response.dto'; import { isInvalidSortMethod } from './feedback.common'; import { FeedbackEntity } from './feedback.entity'; @@ -311,7 +312,10 @@ export class FeedbackMySQLService { this.cls.set('addIssueInFeedback', { feedbackId, issueId }); - await this.feedbackRepository.save(feedback); + await this.feedbackRepository.save({ + ...feedback, + updatedAt: dayjs().format('YYYY-MM-DD HH:mm:ss'), + }); await this.issueRepository.update(dto.issueId, { feedbackCount: () => 'feedback_count + 1', @@ -345,7 +349,10 @@ export class FeedbackMySQLService { feedback.issues = feedback.issues.filter((issue) => issue.id !== issueId); this.cls.set('removeIssueInFeedback', { feedbackId, issueId }); - await this.feedbackRepository.save(feedback); + await this.feedbackRepository.save({ + ...feedback, + updatedAt: dayjs().format('YYYY-MM-DD HH:mm:ss'), + }); await this.issueRepository.update(dto.issueId, { feedbackCount: () => 'feedback_count - 1', diff --git a/apps/api/src/domains/feedback/feedback.os.service.ts b/apps/api/src/domains/feedback/feedback.os.service.ts index 33fdf0e8f..8823b0e97 100644 --- a/apps/api/src/domains/feedback/feedback.os.service.ts +++ b/apps/api/src/domains/feedback/feedback.os.service.ts @@ -16,23 +16,22 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import dayjs from 'dayjs'; -import { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; +import type { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; import { In, Repository } from 'typeorm'; -import { TimeRange } from '@/common/dtos'; +import type { TimeRange } from '@/common/dtos'; import { FieldFormatEnum, SortMethodEnum } from '@/common/enums'; import { OpensearchRepository } from '@/common/repositories'; - -import { FieldEntity } from '../channel/field/field.entity'; -import { +import type { FieldEntity } from '../channel/field/field.entity'; +import type { + CreateFeedbackOSDto, DeleteByIdsDto, FindFeedbacksByChannelIdDto, ScrollFeedbacksDto, UpdateFeedbackESDto, } from './dtos'; -import { CreateFeedbackOSDto } from './dtos'; -import { OsQueryDto } from './dtos/os-query.dto'; -import { Feedback } from './dtos/responses/find-feedbacks-by-channel-id-response.dto'; +import type { OsQueryDto } from './dtos/os-query.dto'; +import type { Feedback } from './dtos/responses/find-feedbacks-by-channel-id-response.dto'; import { isInvalidSortMethod } from './feedback.common'; import { FeedbackEntity } from './feedback.entity'; @@ -138,7 +137,9 @@ export class FeedbackOSService { return osQuery; } - if (!fieldsByKey.hasOwnProperty(fieldKey)) { + if ( + !Object.prototype.hasOwnProperty.call(fieldsByKey, fieldKey) + ) { throw new BadRequestException('bad key in query'); } @@ -188,7 +189,9 @@ export class FeedbackOSService { sort: Object.keys(sort).length !== 0 ? Object.keys(sort).map((fieldKey) => { - if (!fieldsByKey.hasOwnProperty(fieldKey)) { + if ( + !Object.prototype.hasOwnProperty.call(fieldsByKey, fieldKey) + ) { throw new BadRequestException('bad key in sort'); } const { key, format } = fieldsByKey[fieldKey]; diff --git a/apps/api/src/domains/feedback/feedback.service.spec.ts b/apps/api/src/domains/feedback/feedback.service.spec.ts index 1b2769c94..29a848c6d 100644 --- a/apps/api/src/domains/feedback/feedback.service.spec.ts +++ b/apps/api/src/domains/feedback/feedback.service.spec.ts @@ -18,7 +18,7 @@ import { BadRequestException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { ClsService } from 'nestjs-cls'; -import { Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { FieldFormatEnum, @@ -26,47 +26,21 @@ import { FieldTypeEnum, } from '@/common/enums'; import { OpensearchRepository } from '@/common/repositories'; -import { createFieldDto, getRandomValue } from '@/utils/test-util-fixture'; -import { - MockOpensearchRepository, - getMockProvider, - mockRepository, -} from '@/utils/test-utils'; - +import { createFieldDto, getRandomValue } from '@/test-utils/fixtures'; +import { MockOpensearchRepository } from '@/test-utils/util-functions'; +import { FeedbackServiceProviders } from '../../test-utils/providers/feedback.service.providers'; import { ChannelEntity } from '../channel/channel/channel.entity'; -import { ChannelServiceProviders } from '../channel/channel/channel.service.spec'; import { RESERVED_FIELD_KEYS } from '../channel/field/field.constants'; import { FieldEntity } from '../channel/field/field.entity'; -import { FieldServiceProviders } from '../channel/field/field.service.spec'; -import { OptionServiceProviders } from '../channel/option/option.service.spec'; import { IssueEntity } from '../project/issue/issue.entity'; -import { IssueServiceProviders } from '../project/issue/issue.service.spec'; import { CreateFeedbackDto, FindFeedbacksByChannelIdDto } from './dtos'; import { FeedbackEntity } from './feedback.entity'; -import { FeedbackMySQLService } from './feedback.mysql.service'; -import { FeedbackOSService } from './feedback.os.service'; import { FeedbackService } from './feedback.service'; -const FeedbackServiceProviders = [ - FeedbackService, - FeedbackMySQLService, - { - provide: getRepositoryToken(FeedbackEntity), - useValue: mockRepository(), - }, - ClsService, - ...FieldServiceProviders, - ...IssueServiceProviders, - ...OptionServiceProviders, - ...ChannelServiceProviders, - getMockProvider(OpensearchRepository, MockOpensearchRepository), - FeedbackOSService, -]; - const fieldsFixture = Object.values(FieldFormatEnum).flatMap((format) => Object.values(FieldTypeEnum).flatMap((type) => Object.values(FieldStatusEnum).flatMap((status) => ({ - id: faker.datatype.number(), + id: faker.number.int(), ...createFieldDto({ format, type, @@ -110,11 +84,11 @@ describe('FeedbackService Test Suite', () => { describe('create', () => { it('creating a feedback succeeds with valid inputs', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -132,11 +106,11 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback fails with an invalid channel', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); jest.spyOn(fieldRepo, 'find').mockResolvedValue([]); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -150,13 +124,13 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback fails with a reserved field key', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); const reservedFieldKey = faker.helpers.arrayElement(RESERVED_FIELD_KEYS); - dto.data[reservedFieldKey] = faker.datatype.string(); + dto.data[reservedFieldKey] = faker.string.sample(); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -172,13 +146,13 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback fails with an invalid field key', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); const invalidFieldKey = 'invalidFieldKey'; - dto.data[invalidFieldKey] = faker.datatype.string(); + dto.data[invalidFieldKey] = faker.string.sample(); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -192,10 +166,10 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback fails with an admin field', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); const adminFieldKey = 'adminFieldKey'; - dto.data[adminFieldKey] = faker.datatype.string(); + dto.data[adminFieldKey] = faker.string.sample(); jest.spyOn(fieldRepo, 'find').mockResolvedValue([ ...fieldsFixture, createFieldDto({ @@ -204,7 +178,7 @@ describe('FeedbackService Test Suite', () => { }) as FieldEntity, ]); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -218,10 +192,10 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback fails with an inactive field', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); const inactiveFieldKey = 'inactiveFieldKey'; - dto.data[inactiveFieldKey] = faker.datatype.string(); + dto.data[inactiveFieldKey] = faker.string.sample(); jest.spyOn(fieldRepo, 'find').mockResolvedValue([ ...fieldsFixture, createFieldDto({ @@ -231,7 +205,7 @@ describe('FeedbackService Test Suite', () => { }) as FieldEntity, ]); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -282,7 +256,7 @@ describe('FeedbackService Test Suite', () => { status: FieldStatusEnum.ACTIVE, }); const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = { [field.key]: invalidValue, }; @@ -290,7 +264,7 @@ describe('FeedbackService Test Suite', () => { .spyOn(fieldRepo, 'find') .mockResolvedValue([field] as FieldEntity[]); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), rawData: dto.data, } as FeedbackEntity); @@ -311,32 +285,32 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback succeeds with valid inputs and issue names', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); const issueNames = Array.from({ - length: faker.datatype.number({ min: 1, max: 1 }), - }).map(() => faker.datatype.string()); - dto.data.issueNames = [...issueNames, faker.datatype.string()]; - const feedbackId = faker.datatype.number(); + length: faker.number.int({ min: 1, max: 1 }), + }).map(() => faker.string.sample()); + dto.data.issueNames = [...issueNames, faker.string.sample()]; + const feedbackId = faker.number.int(); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ id: feedbackId, rawData: dto.data, } as FeedbackEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce({ - id: faker.datatype.number(), + id: faker.number.int(), name: issueNames[0], } as IssueEntity); jest.spyOn(channelRepo, 'findOne').mockResolvedValue({ id: dto.channelId, fields: [], project: { - id: faker.datatype.number(), + id: faker.number.int(), }, } as ChannelEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce(null); jest.spyOn(issueRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), } as IssueEntity); jest.spyOn(feedbackRepo, 'findOne').mockResolvedValue({ id: feedbackId, @@ -355,20 +329,20 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback succeeds with valid inputs and an existent issue name', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); const issueNames = Array.from({ - length: faker.datatype.number({ min: 1, max: 1 }), - }).map(() => faker.datatype.string()); + length: faker.number.int({ min: 1, max: 1 }), + }).map(() => faker.string.sample()); dto.data.issueNames = [...issueNames]; - const feedbackId = faker.datatype.number(); + const feedbackId = faker.number.int(); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ id: feedbackId, rawData: dto.data, } as FeedbackEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce({ - id: faker.datatype.number(), + id: faker.number.int(), name: issueNames[0], } as IssueEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce(null); @@ -387,10 +361,10 @@ describe('FeedbackService Test Suite', () => { }); it('creating a feedback succeeds with valid inputs and a nonexistent issue name', async () => { const dto = new CreateFeedbackDto(); - dto.channelId = faker.datatype.number(); + dto.channelId = faker.number.int(); dto.data = JSON.parse(JSON.stringify(feedbackFixture)); - dto.data.issueNames = [faker.datatype.string()]; - const feedbackId = faker.datatype.number(); + dto.data.issueNames = [faker.string.sample()]; + const feedbackId = faker.number.int(); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(feedbackRepo, 'save').mockResolvedValue({ id: feedbackId, @@ -401,12 +375,12 @@ describe('FeedbackService Test Suite', () => { id: dto.channelId, fields: [], project: { - id: faker.datatype.number(), + id: faker.number.int(), }, } as ChannelEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce(null); jest.spyOn(issueRepo, 'save').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), } as IssueEntity); jest.spyOn(feedbackRepo, 'findOne').mockResolvedValue({ id: feedbackId, @@ -428,7 +402,7 @@ describe('FeedbackService Test Suite', () => { describe('findByChannelId', () => { describe('with os use', () => { it('finding feedbacks succeeds with valid inputs', async () => { - const channelId = faker.datatype.number(); + const channelId = faker.number.int(); const dto = new FindFeedbacksByChannelIdDto(); dto.channelId = channelId; dto.limit = 10; diff --git a/apps/api/src/domains/feedback/feedback.service.ts b/apps/api/src/domains/feedback/feedback.service.ts index 256e6f0c9..6d24f0dbe 100644 --- a/apps/api/src/domains/feedback/feedback.service.ts +++ b/apps/api/src/domains/feedback/feedback.service.ts @@ -13,18 +13,19 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { createReadStream, existsSync } from 'fs'; +import * as fs from 'fs/promises'; +import path from 'path'; +import { PassThrough } from 'stream'; import { BadRequestException, Injectable, StreamableFile, } from '@nestjs/common'; +import dayjs from 'dayjs'; import * as ExcelJS from 'exceljs'; import * as fastcsv from 'fast-csv'; -import { createReadStream, existsSync } from 'fs'; -import * as fs from 'fs/promises'; -import { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; -import path from 'path'; -import { PassThrough } from 'stream'; +import type { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; import { Transactional } from 'typeorm-transactional'; import { @@ -33,24 +34,25 @@ import { FieldTypeEnum, } from '@/common/enums'; import { OS_USE } from '@/configs/opensearch.config'; - import { ChannelService } from '../channel/channel/channel.service'; import { RESERVED_FIELD_KEYS } from '../channel/field/field.constants'; -import { FieldEntity } from '../channel/field/field.entity'; +import type { FieldEntity } from '../channel/field/field.entity'; import { FieldService } from '../channel/field/field.service'; import { OptionService } from '../channel/option/option.service'; import { IssueService } from '../project/issue/issue.service'; +import type { + CountByProjectIdDto, + FindFeedbacksByChannelIdDto, + GenerateExcelDto, +} from './dtos'; import { AddIssueDto, - CountByProjectIdDto, CreateFeedbackDto, DeleteByIdsDto, - FindFeedbacksByChannelIdDto, - GenerateExcelDto, RemoveIssueDto, UpdateFeedbackDto, } from './dtos'; -import { Feedback } from './dtos/responses/find-feedbacks-by-channel-id-response.dto'; +import type { Feedback } from './dtos/responses/find-feedbacks-by-channel-id-response.dto'; import { validateValue } from './feedback.common'; import { FeedbackMySQLService } from './feedback.mysql.service'; import { FeedbackOSService } from './feedback.os.service'; @@ -105,8 +107,8 @@ export class FeedbackService { if ( typeof query[fieldKey] !== 'object' || !( - query[fieldKey].hasOwnProperty('gte') && - query[fieldKey].hasOwnProperty('lt') + Object.prototype.hasOwnProperty.call(query[fieldKey], 'gte') && + Object.prototype.hasOwnProperty.call(query[fieldKey], 'lt') ) ) throw new BadRequestException(`${fieldKey} must be DateTimeRange`); @@ -475,12 +477,28 @@ export class FeedbackService { @Transactional() async addIssue(dto: AddIssueDto) { - return this.feedbackMySQLService.addIssue(dto); + await this.feedbackMySQLService.addIssue(dto); + + if (OS_USE) { + await this.feedbackOSService.upsertFeedbackItem({ + channelId: dto.channelId, + feedbackId: dto.feedbackId, + data: { updatedAt: dayjs().toISOString() }, + }); + } } @Transactional() async removeIssue(dto: RemoveIssueDto) { - return this.feedbackMySQLService.removeIssue(dto); + await this.feedbackMySQLService.removeIssue(dto); + + if (OS_USE) { + await this.feedbackOSService.upsertFeedbackItem({ + channelId: dto.channelId, + feedbackId: dto.feedbackId, + data: { updatedAt: dayjs().toISOString() }, + }); + } } async countByProjectId(dto: CountByProjectIdDto) { diff --git a/apps/api/src/domains/history/create-history.dto.ts b/apps/api/src/domains/history/create-history.dto.ts index 4139aceb0..3a64c701d 100644 --- a/apps/api/src/domains/history/create-history.dto.ts +++ b/apps/api/src/domains/history/create-history.dto.ts @@ -13,8 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { HistoryActionEnum } from './history-action.enum'; -import { EntityNameEnum } from './history-entity.enum'; +import type { HistoryActionEnum } from './history-action.enum'; +import type { EntityNameEnum } from './history-entity.enum'; export class CreateHistoryDto { userId?: number | null; diff --git a/apps/api/src/domains/history/history.entity.ts b/apps/api/src/domains/history/history.entity.ts index c7610ebb3..1f02e3c53 100644 --- a/apps/api/src/domains/history/history.entity.ts +++ b/apps/api/src/domains/history/history.entity.ts @@ -16,7 +16,6 @@ import { Column, Entity, ManyToOne, Relation } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { UserEntity } from '../user/entities/user.entity'; import { HistoryActionEnum } from './history-action.enum'; import { EntityNameEnum } from './history-entity.enum'; diff --git a/apps/api/src/domains/history/history.service.ts b/apps/api/src/domains/history/history.service.ts index 471bbe3c8..399b214ee 100644 --- a/apps/api/src/domains/history/history.service.ts +++ b/apps/api/src/domains/history/history.service.ts @@ -17,7 +17,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; -import { CreateHistoryDto } from './create-history.dto'; +import type { CreateHistoryDto } from './create-history.dto'; import { HistoryEntity } from './history.entity'; @Injectable() diff --git a/apps/api/src/domains/history/subscribers/abstract-history.subscriber.ts b/apps/api/src/domains/history/subscribers/abstract-history.subscriber.ts index 3602a85dd..c2c25e827 100644 --- a/apps/api/src/domains/history/subscribers/abstract-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/abstract-history.subscriber.ts @@ -15,8 +15,7 @@ */ import { InjectDataSource } from '@nestjs/typeorm'; import { ClsService } from 'nestjs-cls'; -import { - DataSource, +import type { EntitySubscriberInterface, InsertEvent, ObjectLiteral, @@ -25,9 +24,9 @@ import { SoftRemoveEvent, UpdateEvent, } from 'typeorm'; +import { DataSource } from 'typeorm'; -import { CommonEntity } from '@/common/entities'; - +import type { CommonEntity } from '@/common/entities'; import { HistoryActionEnum } from '../history-action.enum'; import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; diff --git a/apps/api/src/domains/history/subscribers/api-key-history.subscriber.ts b/apps/api/src/domains/history/subscribers/api-key-history.subscriber.ts index eb4f278b5..21ae776c5 100644 --- a/apps/api/src/domains/history/subscribers/api-key-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/api-key-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { ApiKeyEntity } from '@/domains/project/api-key/api-key.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/channel-history.subscriber.ts b/apps/api/src/domains/history/subscribers/channel-history.subscriber.ts index dfba3d013..4a7f39dcd 100644 --- a/apps/api/src/domains/history/subscribers/channel-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/channel-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { ChannelEntity } from '@/domains/channel/channel/channel.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/code-history.subscriber.ts b/apps/api/src/domains/history/subscribers/code-history.subscriber.ts index 7c7113f7a..99aa608bf 100644 --- a/apps/api/src/domains/history/subscribers/code-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/code-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { CodeEntity } from '@/shared/code/code.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/feedback-history.subscriber.ts b/apps/api/src/domains/history/subscribers/feedback-history.subscriber.ts index 619dd7702..9eae2d164 100644 --- a/apps/api/src/domains/history/subscribers/feedback-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/feedback-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { FeedbackEntity } from '@/domains/feedback/feedback.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/feedback-issue-history.subscriber.ts b/apps/api/src/domains/history/subscribers/feedback-issue-history.subscriber.ts index 24759dc9d..967a18dd9 100644 --- a/apps/api/src/domains/history/subscribers/feedback-issue-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/feedback-issue-history.subscriber.ts @@ -15,14 +15,10 @@ */ import { InjectDataSource } from '@nestjs/typeorm'; import { ClsService } from 'nestjs-cls'; -import { - DataSource, - EntitySubscriberInterface, - EventSubscriber, -} from 'typeorm'; - -import { ClsServiceType } from '@/types/cls-service.type'; +import type { EntitySubscriberInterface } from 'typeorm'; +import { DataSource, EventSubscriber } from 'typeorm'; +import type { ClsServiceType } from '@/types/cls-service.type'; import { HistoryActionEnum } from '../history-action.enum'; import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; diff --git a/apps/api/src/domains/history/subscribers/field-history.subscriber.ts b/apps/api/src/domains/history/subscribers/field-history.subscriber.ts index c151633d6..8dbbcf839 100644 --- a/apps/api/src/domains/history/subscribers/field-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/field-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { FieldEntity } from '@/domains/channel/field/field.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/issue-history.subscriber.ts b/apps/api/src/domains/history/subscribers/issue-history.subscriber.ts index 02834bf82..425407b83 100644 --- a/apps/api/src/domains/history/subscribers/issue-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/issue-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { IssueEntity } from '@/domains/project/issue/issue.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/issue-tracker-history.subscriber.ts b/apps/api/src/domains/history/subscribers/issue-tracker-history.subscriber.ts index 4e58b2abc..637eb3248 100644 --- a/apps/api/src/domains/history/subscribers/issue-tracker-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/issue-tracker-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { IssueTrackerEntity } from '@/domains/project/issue-tracker/issue-tracker.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/member-history.subscriber.ts b/apps/api/src/domains/history/subscribers/member-history.subscriber.ts index 51619d8c5..bc858a45d 100644 --- a/apps/api/src/domains/history/subscribers/member-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/member-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { MemberEntity } from '@/domains/project/member/member.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/option-history.subscriber.ts b/apps/api/src/domains/history/subscribers/option-history.subscriber.ts index eb761ee1c..edaac91c7 100644 --- a/apps/api/src/domains/history/subscribers/option-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/option-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { OptionEntity } from '@/domains/channel/option/option.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/project-history.subscriber.ts b/apps/api/src/domains/history/subscribers/project-history.subscriber.ts index 66f1d67d4..24f83b8ad 100644 --- a/apps/api/src/domains/history/subscribers/project-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/project-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { ProjectEntity } from '@/domains/project/project/project.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/role-history.subscriber.ts b/apps/api/src/domains/history/subscribers/role-history.subscriber.ts index 89294f9d1..06def656e 100644 --- a/apps/api/src/domains/history/subscribers/role-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/role-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { RoleEntity } from '@/domains/project/role/role.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/tenant-history.subscriber.ts b/apps/api/src/domains/history/subscribers/tenant-history.subscriber.ts index a520347e7..7b2fc461a 100644 --- a/apps/api/src/domains/history/subscribers/tenant-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/tenant-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { TenantEntity } from '@/domains/tenant/tenant.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/history/subscribers/user-history.subscriber.ts b/apps/api/src/domains/history/subscribers/user-history.subscriber.ts index a910f407e..316b23412 100644 --- a/apps/api/src/domains/history/subscribers/user-history.subscriber.ts +++ b/apps/api/src/domains/history/subscribers/user-history.subscriber.ts @@ -18,7 +18,6 @@ import { ClsService } from 'nestjs-cls'; import { DataSource, EventSubscriber } from 'typeorm'; import { UserEntity } from '@/domains/user/entities/user.entity'; - import { EntityNameEnum } from '../history-entity.enum'; import { HistoryService } from '../history.service'; import { AbstractHistorySubscriber } from './abstract-history.subscriber'; diff --git a/apps/api/src/domains/migration/migration.module.ts b/apps/api/src/domains/migration/migration.module.ts index 98cc37c29..a8a340d26 100644 --- a/apps/api/src/domains/migration/migration.module.ts +++ b/apps/api/src/domains/migration/migration.module.ts @@ -13,12 +13,12 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Module, OnModuleInit } from '@nestjs/common'; +import type { OnModuleInit } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpensearchRepository } from '@/common/repositories'; import { OS_USE } from '@/configs/opensearch.config'; - import { ChannelEntity } from '../channel/channel/channel.entity'; import { FieldEntity } from '../channel/field/field.entity'; import { FieldModule } from '../channel/field/field.module'; diff --git a/apps/api/src/domains/migration/migration.service.ts b/apps/api/src/domains/migration/migration.service.ts index 87319aaaf..3b8f69087 100644 --- a/apps/api/src/domains/migration/migration.service.ts +++ b/apps/api/src/domains/migration/migration.service.ts @@ -13,15 +13,13 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Inject, Logger } from '@nestjs/common'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Client } from '@opensearch-project/opensearch'; import dayjs from 'dayjs'; import { Repository } from 'typeorm'; import { OpensearchRepository } from '@/common/repositories'; - import { ChannelEntity } from '../channel/channel/channel.entity'; import { FieldService } from '../channel/field/field.service'; import { FeedbackEntity } from '../feedback/feedback.entity'; diff --git a/apps/api/src/domains/project/api-key/api-key.controller.spec.ts b/apps/api/src/domains/project/api-key/api-key.controller.spec.ts index ee7b476c2..93b602660 100644 --- a/apps/api/src/domains/project/api-key/api-key.controller.spec.ts +++ b/apps/api/src/domains/project/api-key/api-key.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { DataSource } from 'typeorm'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { ApiKeyController } from './api-key.controller'; import { ApiKeyService } from './api-key.service'; @@ -49,7 +48,7 @@ describe('ApiKeyController', () => { describe('create', () => { it('', async () => { jest.spyOn(MockApiKeyService, 'create'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await apiKeyController.create(projectId); @@ -59,7 +58,7 @@ describe('ApiKeyController', () => { describe('findAll', () => { it('', async () => { jest.spyOn(MockApiKeyService, 'findAllByProjectId'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await apiKeyController.findAll(projectId); @@ -69,7 +68,7 @@ describe('ApiKeyController', () => { describe('softDelete', () => { it('', async () => { jest.spyOn(MockApiKeyService, 'softDeleteById'); - const apiKeyId = faker.datatype.number(); + const apiKeyId = faker.number.int(); await apiKeyController.softDelete(apiKeyId); @@ -79,7 +78,7 @@ describe('ApiKeyController', () => { describe('recover', () => { it('', async () => { jest.spyOn(MockApiKeyService, 'recoverById'); - const apiKeyId = faker.datatype.number(); + const apiKeyId = faker.number.int(); await apiKeyController.recover(apiKeyId); @@ -89,7 +88,7 @@ describe('ApiKeyController', () => { describe('delete', () => { it('', async () => { jest.spyOn(MockApiKeyService, 'deleteById'); - const apiKeyId = faker.datatype.number(); + const apiKeyId = faker.number.int(); await apiKeyController.delete(apiKeyId); diff --git a/apps/api/src/domains/project/api-key/api-key.entity.ts b/apps/api/src/domains/project/api-key/api-key.entity.ts index 2fdabb837..ebba86846 100644 --- a/apps/api/src/domains/project/api-key/api-key.entity.ts +++ b/apps/api/src/domains/project/api-key/api-key.entity.ts @@ -16,7 +16,6 @@ import { Column, Entity, ManyToOne, Relation } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { ProjectEntity } from '../project/project.entity'; @Entity('api_keys') diff --git a/apps/api/src/domains/project/api-key/api-key.service.spec.ts b/apps/api/src/domains/project/api-key/api-key.service.spec.ts index f6642e48f..3e4f4a26d 100644 --- a/apps/api/src/domains/project/api-key/api-key.service.spec.ts +++ b/apps/api/src/domains/project/api-key/api-key.service.spec.ts @@ -13,29 +13,18 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { randomBytes } from 'crypto'; import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { randomBytes } from 'crypto'; -import { Repository } from 'typeorm'; - -import { mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { ApiKeyServiceProviders } from '../../../test-utils/providers/api-key.service.providers'; import { ProjectNotFoundException } from '../project/exceptions'; import { ProjectEntity } from '../project/project.entity'; -import { ProjectServiceProviders } from '../project/project.service.spec'; import { ApiKeyEntity } from './api-key.entity'; import { ApiKeyService } from './api-key.service'; -export const ApiKeyServiceProviders = [ - ApiKeyService, - { - provide: getRepositoryToken(ApiKeyEntity), - useValue: mockRepository(), - }, - ...ProjectServiceProviders, -]; - describe('ApiKeyService', () => { let apiKeyService: ApiKeyService; let apiKeyRepo: Repository; @@ -53,7 +42,7 @@ describe('ApiKeyService', () => { describe('create', () => { it('creating an api key succeeds with a valid project id', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); jest .spyOn(projectRepo, 'findOneBy') .mockResolvedValueOnce({ id: projectId } as ProjectEntity); @@ -69,7 +58,7 @@ describe('ApiKeyService', () => { expect(apiKey.value).toHaveLength(20); }); it('creating an api key fails with an invalid project id', async () => { - const invalidProjectId = faker.datatype.number(); + const invalidProjectId = faker.number.int(); jest .spyOn(projectRepo, 'findOneBy') .mockResolvedValueOnce(null as ProjectEntity); diff --git a/apps/api/src/domains/project/api-key/api-key.service.ts b/apps/api/src/domains/project/api-key/api-key.service.ts index 09870a1df..31b79e38e 100644 --- a/apps/api/src/domains/project/api-key/api-key.service.ts +++ b/apps/api/src/domains/project/api-key/api-key.service.ts @@ -13,9 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { randomBytes } from 'crypto'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { randomBytes } from 'crypto'; import dayjs from 'dayjs'; import { Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; diff --git a/apps/api/src/domains/project/api-key/dtos/responses/find-api-keys-response.dto.ts b/apps/api/src/domains/project/api-key/dtos/responses/find-api-keys-response.dto.ts index b0c9939df..202ce7ce7 100644 --- a/apps/api/src/domains/project/api-key/dtos/responses/find-api-keys-response.dto.ts +++ b/apps/api/src/domains/project/api-key/dtos/responses/find-api-keys-response.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; class ApiKeyResponseDto { @Expose() diff --git a/apps/api/src/domains/project/issue-tracker/issue-tracker.controller.spec.ts b/apps/api/src/domains/project/issue-tracker/issue-tracker.controller.spec.ts index 91a4fd329..524726a67 100644 --- a/apps/api/src/domains/project/issue-tracker/issue-tracker.controller.spec.ts +++ b/apps/api/src/domains/project/issue-tracker/issue-tracker.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { DataSource } from 'typeorm'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { IssueTrackerController } from './issue-tracker.controller'; import { IssueTrackerService } from './issue-tracker.service'; @@ -46,7 +45,7 @@ describe('IssueTrackerController', () => { describe('create', () => { it('', async () => { jest.spyOn(MockIssueTrackerService, 'create'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await issueTrackerController.create(projectId, { data: {} }); expect(MockIssueTrackerService.create).toBeCalledTimes(1); @@ -55,7 +54,7 @@ describe('IssueTrackerController', () => { describe('findOne', () => { it('', async () => { jest.spyOn(MockIssueTrackerService, 'findByProjectId'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await issueTrackerController.findOne(projectId); expect(MockIssueTrackerService.findByProjectId).toBeCalledTimes(1); @@ -64,7 +63,7 @@ describe('IssueTrackerController', () => { describe('updateOne', () => { it('', async () => { jest.spyOn(MockIssueTrackerService, 'update'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await issueTrackerController.updateOne(projectId, { data: {} }); expect(MockIssueTrackerService.update).toBeCalledTimes(1); diff --git a/apps/api/src/domains/project/issue-tracker/issue-tracker.entity.ts b/apps/api/src/domains/project/issue-tracker/issue-tracker.entity.ts index ea7f30157..cd3c38031 100644 --- a/apps/api/src/domains/project/issue-tracker/issue-tracker.entity.ts +++ b/apps/api/src/domains/project/issue-tracker/issue-tracker.entity.ts @@ -16,9 +16,8 @@ import { Column, Entity, JoinColumn, OneToOne, Relation } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { ProjectEntity } from '../project/project.entity'; -import { CreateIssueTrackerDto } from './dtos'; +import type { CreateIssueTrackerDto } from './dtos'; @Entity('issue_trackers') export class IssueTrackerEntity extends CommonEntity { diff --git a/apps/api/src/domains/project/issue-tracker/issue-tracker.service.spec.ts b/apps/api/src/domains/project/issue-tracker/issue-tracker.service.spec.ts index 2e707aba2..5363fafb5 100644 --- a/apps/api/src/domains/project/issue-tracker/issue-tracker.service.spec.ts +++ b/apps/api/src/domains/project/issue-tracker/issue-tracker.service.spec.ts @@ -16,22 +16,13 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { IssueTrackerServiceProviders } from '../../../test-utils/providers/issue-tracker.service.provider'; import { UpdateIssueTrackerDto } from './dtos'; import { IssueTrackerEntity } from './issue-tracker.entity'; import { IssueTrackerService } from './issue-tracker.service'; -const IssueTrackerServiceProviders = [ - IssueTrackerService, - { - provide: getRepositoryToken(IssueTrackerEntity), - useValue: mockRepository(), - }, -]; - describe('issue-tracker service', () => { let issueTrackerService: IssueTrackerService; let issueTrackerRepo: Repository; @@ -46,11 +37,11 @@ describe('issue-tracker service', () => { describe('update', () => { it('updating a issue tracker succeeds with a valid project id', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const dto = new UpdateIssueTrackerDto(); dto.projectId = projectId; dto.data = { - ticketKey: faker.datatype.string(), + ticketKey: faker.string.sample(), ticketDomain: faker.internet.domainName(), }; jest diff --git a/apps/api/src/domains/project/issue/dtos/find-issues-by-project-id.dto.ts b/apps/api/src/domains/project/issue/dtos/find-issues-by-project-id.dto.ts index 1305eddac..7b4aa63a7 100644 --- a/apps/api/src/domains/project/issue/dtos/find-issues-by-project-id.dto.ts +++ b/apps/api/src/domains/project/issue/dtos/find-issues-by-project-id.dto.ts @@ -13,9 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { PaginationDto, TimeRange } from '@/common/dtos'; - -import { SortMethodEnum } from '../../../../common/enums'; +import type { TimeRange } from '@/common/dtos'; +import { PaginationDto } from '@/common/dtos'; +import type { SortMethodEnum } from '../../../../common/enums'; export class FindIssuesByProjectIdDto extends PaginationDto { projectId: number; diff --git a/apps/api/src/domains/project/issue/dtos/requests/find-issues-by-project-id-request.dto.ts b/apps/api/src/domains/project/issue/dtos/requests/find-issues-by-project-id-request.dto.ts index 9affc746d..e615cc8ca 100644 --- a/apps/api/src/domains/project/issue/dtos/requests/find-issues-by-project-id-request.dto.ts +++ b/apps/api/src/domains/project/issue/dtos/requests/find-issues-by-project-id-request.dto.ts @@ -16,8 +16,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; -import { PaginationRequestDto, TimeRange } from '@/common/dtos'; -import { SortMethodEnum } from '@/common/enums'; +import type { TimeRange } from '@/common/dtos'; +import { PaginationRequestDto } from '@/common/dtos'; +import type { SortMethodEnum } from '@/common/enums'; export class FindIssuesByProjectIdRequestDto extends PaginationRequestDto { @ApiProperty({ type: Object, required: false }) diff --git a/apps/api/src/domains/project/issue/dtos/requests/update-issue-request.dto.ts b/apps/api/src/domains/project/issue/dtos/requests/update-issue-request.dto.ts index 710cf36af..6576113c9 100644 --- a/apps/api/src/domains/project/issue/dtos/requests/update-issue-request.dto.ts +++ b/apps/api/src/domains/project/issue/dtos/requests/update-issue-request.dto.ts @@ -18,7 +18,6 @@ import { IsEnum, IsOptional, IsString } from 'class-validator'; import { IssueStatusEnum } from '@/common/enums'; import { IsNullable } from '@/domains/user/decorators'; - import { CreateIssueRequestDto } from './create-issue-request.dto'; export class UpdateIssueRequestDto extends CreateIssueRequestDto { diff --git a/apps/api/src/domains/project/issue/dtos/responses/find-issues-by-project-id-response.dto.ts b/apps/api/src/domains/project/issue/dtos/responses/find-issues-by-project-id-response.dto.ts index fb75725df..7aef21bc2 100644 --- a/apps/api/src/domains/project/issue/dtos/responses/find-issues-by-project-id-response.dto.ts +++ b/apps/api/src/domains/project/issue/dtos/responses/find-issues-by-project-id-response.dto.ts @@ -14,10 +14,9 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PaginationResponseDto } from '@/common/dtos'; - import { FindIssueByIdResponseDto } from './find-issue-by-id-response.dto'; export class FindIssuesByProjectIdResponseDto extends PaginationResponseDto { diff --git a/apps/api/src/domains/project/issue/issue.controller.spec.ts b/apps/api/src/domains/project/issue/issue.controller.spec.ts index cab38dc89..5367e67e6 100644 --- a/apps/api/src/domains/project/issue/issue.controller.spec.ts +++ b/apps/api/src/domains/project/issue/issue.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { DataSource } from 'typeorm'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { CreateIssueRequestDto, DeleteIssuesRequestDto, @@ -56,9 +55,9 @@ describe('IssueController', () => { it('should return a saved id', async () => { jest.spyOn(MockIssueService, 'create'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const dto = new CreateIssueRequestDto(); - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); await issueController.create(projectId, dto); @@ -67,7 +66,7 @@ describe('IssueController', () => { }); describe('findById', () => { it('should return an issue', async () => { - const issueId = faker.datatype.number(); + const issueId = faker.number.int(); const issue = new IssueEntity(); jest.spyOn(MockIssueService, 'findById').mockReturnValue(issue); @@ -78,7 +77,7 @@ describe('IssueController', () => { }); describe('findAllByProjectId', () => { it('should return issues', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const issues = [new IssueEntity()]; jest .spyOn(MockIssueService, 'findIssuesByProjectId') @@ -94,11 +93,11 @@ describe('IssueController', () => { }); describe('update', () => { it('', async () => { - const projectId = faker.datatype.number(); - const issueId = faker.datatype.number(); + const projectId = faker.number.int(); + const issueId = faker.number.int(); const dto = new UpdateIssueRequestDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); jest.spyOn(MockIssueService, 'update'); await issueController.update(projectId, issueId, dto); @@ -108,7 +107,7 @@ describe('IssueController', () => { }); describe('delete', () => { it('', async () => { - const issueId = faker.datatype.number(); + const issueId = faker.number.int(); jest.spyOn(MockIssueService, 'deleteById'); await issueController.delete(issueId); @@ -118,9 +117,9 @@ describe('IssueController', () => { }); describe('deleteMany', () => { it('', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const dto = new DeleteIssuesRequestDto(); - dto.issueIds = [faker.datatype.number()]; + dto.issueIds = [faker.number.int()]; jest.spyOn(MockIssueService, 'deleteByIds'); await issueController.deleteMany(projectId, dto); diff --git a/apps/api/src/domains/project/issue/issue.entity.ts b/apps/api/src/domains/project/issue/issue.entity.ts index 55b76c93f..722a19773 100644 --- a/apps/api/src/domains/project/issue/issue.entity.ts +++ b/apps/api/src/domains/project/issue/issue.entity.ts @@ -25,7 +25,6 @@ import { import { CommonEntity } from '@/common/entities'; import { IssueStatusEnum } from '@/common/enums'; - import { FeedbackEntity } from '../../feedback/feedback.entity'; import { ProjectEntity } from '../project/project.entity'; diff --git a/apps/api/src/domains/project/issue/issue.service.spec.ts b/apps/api/src/domains/project/issue/issue.service.spec.ts index 41208a0c1..9672c00c8 100644 --- a/apps/api/src/domains/project/issue/issue.service.spec.ts +++ b/apps/api/src/domains/project/issue/issue.service.spec.ts @@ -16,11 +16,12 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Like, Repository } from 'typeorm'; - -import { TimeRange } from '@/common/dtos'; -import { createQueryBuilder, mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { Like } from 'typeorm'; +import type { TimeRange } from '@/common/dtos'; +import { createQueryBuilder } from '@/test-utils/util-functions'; +import { IssueServiceProviders } from '../../../test-utils/providers/issue.service.providers'; import { CreateIssueDto, FindIssuesByProjectIdDto, @@ -33,14 +34,6 @@ import { import { IssueEntity } from './issue.entity'; import { IssueService } from './issue.service'; -export const IssueServiceProviders = [ - IssueService, - { - provide: getRepositoryToken(IssueEntity), - useValue: mockRepository(), - }, -]; - describe('IssueService test suite', () => { let issueService: IssueService; let issueRepo: Repository; @@ -55,7 +48,7 @@ describe('IssueService test suite', () => { }); describe('create', () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); let dto: CreateIssueDto; beforeEach(() => { dto = new CreateIssueDto(); @@ -63,7 +56,7 @@ describe('IssueService test suite', () => { }); it('creating an issue succeeds with valid inputs', async () => { - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValue(null as IssueEntity); jest.spyOn(issueRepo, 'save').mockResolvedValue({} as IssueEntity); @@ -90,7 +83,7 @@ describe('IssueService test suite', () => { }); describe('findIssuesByProjectId', () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); let dto: FindIssuesByProjectIdDto; beforeEach(() => { dto = new FindIssuesByProjectIdDto(); @@ -160,7 +153,7 @@ describe('IssueService test suite', () => { }); }); it('finding issues succeeds with the id query', async () => { - const id = faker.datatype.number(); + const id = faker.number.int(); dto.query = { id, }; @@ -289,16 +282,16 @@ describe('IssueService test suite', () => { }); describe('update', () => { - const issueId = faker.datatype.number(); + const issueId = faker.number.int(); let dto: UpdateIssueDto; beforeEach(() => { dto = new UpdateIssueDto(); dto.issueId = issueId; - dto.description = faker.datatype.string(); + dto.description = faker.string.sample(); }); it('updating an issue succeeds with valid inputs', async () => { - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValue({ id: issueId, } as IssueEntity); diff --git a/apps/api/src/domains/project/issue/issue.service.ts b/apps/api/src/domains/project/issue/issue.service.ts index 56cce6b13..72ad081e9 100644 --- a/apps/api/src/domains/project/issue/issue.service.ts +++ b/apps/api/src/domains/project/issue/issue.service.ts @@ -16,26 +16,14 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { paginate } from 'nestjs-typeorm-paginate'; -import { - FindManyOptions, - FindOptionsWhere, - In, - Like, - Not, - Raw, - Repository, -} from 'typeorm'; +import type { FindManyOptions, FindOptionsWhere } from 'typeorm'; +import { In, Like, Not, Raw, Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; -import { TimeRange } from '@/common/dtos'; -import { CountByProjectIdDto } from '@/domains/feedback/dtos'; - -import { - CreateIssueDto, - FindByIssueIdDto, - FindIssuesByProjectIdDto, - UpdateIssueDto, -} from './dtos'; +import type { TimeRange } from '@/common/dtos'; +import type { CountByProjectIdDto } from '@/domains/feedback/dtos'; +import type { FindByIssueIdDto, FindIssuesByProjectIdDto } from './dtos'; +import { CreateIssueDto, UpdateIssueDto } from './dtos'; import { IssueInvalidNameException, IssueNameDuplicatedException, diff --git a/apps/api/src/domains/project/member/dtos/find-by-project-id.dto.ts b/apps/api/src/domains/project/member/dtos/find-by-project-id.dto.ts index 846c50caf..cff53fdb4 100644 --- a/apps/api/src/domains/project/member/dtos/find-by-project-id.dto.ts +++ b/apps/api/src/domains/project/member/dtos/find-by-project-id.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { SortMethodEnum } from '@/common/enums'; +import type { SortMethodEnum } from '@/common/enums'; export class FindByProjectIdDto { projectId: number; diff --git a/apps/api/src/domains/project/member/dtos/responses/get-all-members.dto.ts b/apps/api/src/domains/project/member/dtos/responses/get-all-members.dto.ts index d7bef1a57..360a1b209 100644 --- a/apps/api/src/domains/project/member/dtos/responses/get-all-members.dto.ts +++ b/apps/api/src/domains/project/member/dtos/responses/get-all-members.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PermissionEnum } from '@/domains/project/role/permission.enum'; diff --git a/apps/api/src/domains/project/member/member.controller.ts b/apps/api/src/domains/project/member/member.controller.ts index ce234232c..bd9c77302 100644 --- a/apps/api/src/domains/project/member/member.controller.ts +++ b/apps/api/src/domains/project/member/member.controller.ts @@ -27,7 +27,6 @@ import { import { ApiOkResponse, ApiParam, ApiTags } from '@nestjs/swagger'; import { SortMethodEnum } from '@/common/enums'; - import { PermissionEnum } from '../role/permission.enum'; import { RequirePermission } from '../role/require-permission.decorator'; import { diff --git a/apps/api/src/domains/project/member/member.entity.ts b/apps/api/src/domains/project/member/member.entity.ts index d5a911071..3079c7f8d 100644 --- a/apps/api/src/domains/project/member/member.entity.ts +++ b/apps/api/src/domains/project/member/member.entity.ts @@ -16,7 +16,6 @@ import { Entity, ManyToOne, Relation, Unique } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { UserEntity } from '../../user/entities/user.entity'; import { RoleEntity } from '../role/role.entity'; diff --git a/apps/api/src/domains/project/member/member.service.spec.ts b/apps/api/src/domains/project/member/member.service.spec.ts index 87fc3fa8d..065e07dc4 100644 --- a/apps/api/src/domains/project/member/member.service.spec.ts +++ b/apps/api/src/domains/project/member/member.service.spec.ts @@ -16,12 +16,10 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { MemberServiceProviders } from '../../../test-utils/providers/member.service.providers'; import { RoleEntity } from '../role/role.entity'; -import { RoleServiceProviders } from '../role/role.service.spec'; import { CreateMemberDto, UpdateMemberDto } from './dtos'; import { MemberAlreadyExistsException, @@ -31,15 +29,6 @@ import { import { MemberEntity } from './member.entity'; import { MemberService } from './member.service'; -export const MemberServiceProviders = [ - MemberService, - { - provide: getRepositoryToken(MemberEntity), - useValue: mockRepository(), - }, - ...RoleServiceProviders, -]; - describe('MemberService test suite', () => { let memberService: MemberService; let memberRepo: Repository; @@ -56,9 +45,9 @@ describe('MemberService test suite', () => { }); describe('create', () => { - const projectId = faker.datatype.number(); - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const projectId = faker.number.int(); + const roleId = faker.number.int(); + const userId = faker.number.int(); let dto: CreateMemberDto; beforeEach(() => { dto = new CreateMemberDto(); @@ -101,9 +90,9 @@ describe('MemberService test suite', () => { }); describe('update', () => { - const projectId = faker.datatype.number(); - const roleId = faker.datatype.number(); - const memberId = faker.datatype.number(); + const projectId = faker.number.int(); + const roleId = faker.number.int(); + const memberId = faker.number.int(); let dto: UpdateMemberDto; beforeEach(() => { dto = new UpdateMemberDto(); @@ -112,7 +101,7 @@ describe('MemberService test suite', () => { }); it('updating a member succeeds with valid inputs', async () => { - const newRoleId = faker.datatype.number(); + const newRoleId = faker.number.int(); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ project: { id: projectId }, id: newRoleId, @@ -153,7 +142,7 @@ describe('MemberService test suite', () => { .spyOn(roleRepo, 'findOne') .mockResolvedValue({ project: { id: projectId } } as RoleEntity); jest.spyOn(memberRepo, 'findOne').mockResolvedValue({ - role: { id: roleId, project: { id: faker.datatype.number() } }, + role: { id: roleId, project: { id: faker.number.int() } }, } as MemberEntity); jest.spyOn(memberRepo, 'save'); diff --git a/apps/api/src/domains/project/member/member.service.ts b/apps/api/src/domains/project/member/member.service.ts index 132585c5b..f14aec2e4 100644 --- a/apps/api/src/domains/project/member/member.service.ts +++ b/apps/api/src/domains/project/member/member.service.ts @@ -19,7 +19,8 @@ import { Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; import { RoleService } from '../role/role.service'; -import { CreateMemberDto, FindByProjectIdDto, UpdateMemberDto } from './dtos'; +import type { FindByProjectIdDto } from './dtos'; +import { CreateMemberDto, UpdateMemberDto } from './dtos'; import { MemberAlreadyExistsException, MemberNotFoundException, diff --git a/apps/api/src/domains/project/project/dtos/find-all-projects.dto.ts b/apps/api/src/domains/project/project/dtos/find-all-projects.dto.ts index b20d1f1c0..613f9efe3 100644 --- a/apps/api/src/domains/project/project/dtos/find-all-projects.dto.ts +++ b/apps/api/src/domains/project/project/dtos/find-all-projects.dto.ts @@ -13,9 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { IPaginationOptions } from 'nestjs-typeorm-paginate'; +import type { IPaginationOptions } from 'nestjs-typeorm-paginate'; -import { UserDto } from '@/domains/user/dtos'; +import type { UserDto } from '@/domains/user/dtos'; export class FindAllProjectsDto { user: UserDto; diff --git a/apps/api/src/domains/project/project/dtos/responses/find-projects-response.dto.ts b/apps/api/src/domains/project/project/dtos/responses/find-projects-response.dto.ts index 085d8194b..b5b9bbc0f 100644 --- a/apps/api/src/domains/project/project/dtos/responses/find-projects-response.dto.ts +++ b/apps/api/src/domains/project/project/dtos/responses/find-projects-response.dto.ts @@ -14,10 +14,9 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PaginationResponseDto } from '@/common/dtos'; - import { FindProjectByIdResponseDto } from './find-project-by-id-response.dto'; export class FindProjectsResponseDto extends PaginationResponseDto { diff --git a/apps/api/src/domains/project/project/project.controller.spec.ts b/apps/api/src/domains/project/project/project.controller.spec.ts index d7292dd87..9c4ebbfba 100644 --- a/apps/api/src/domains/project/project/project.controller.spec.ts +++ b/apps/api/src/domains/project/project/project.controller.spec.ts @@ -19,8 +19,7 @@ import { DataSource } from 'typeorm'; import { FeedbackService } from '@/domains/feedback/feedback.service'; import { UserDto } from '@/domains/user/dtos'; -import { MockDataSource, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, MockDataSource } from '@/test-utils/util-functions'; import { IssueService } from '../issue/issue.service'; import { CreateProjectRequestDto, @@ -62,8 +61,8 @@ describe('ProjectController', () => { it('should return an array of users', async () => { jest.spyOn(MockProjectService, 'create'); const dto = new CreateProjectRequestDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); await projectController.create(dto); expect(MockProjectService.create).toBeCalledTimes(1); @@ -73,8 +72,8 @@ describe('ProjectController', () => { it('should return an array of users', async () => { jest.spyOn(MockProjectService, 'findAll'); const dto = new FindProjectsRequestDto(); - dto.limit = faker.datatype.number(); - dto.page = faker.datatype.number(); + dto.limit = faker.number.int(); + dto.page = faker.number.int(); const userDto = new UserDto(); await projectController.findAll(dto, userDto); @@ -84,7 +83,7 @@ describe('ProjectController', () => { describe('countFeedbacks', () => { it('should return a number of total feedbacks by project id', async () => { jest.spyOn(MockFeedbackService, 'countByProjectId'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await projectController.countFeedbacks(projectId); expect(MockFeedbackService.countByProjectId).toBeCalledTimes(1); @@ -94,7 +93,7 @@ describe('ProjectController', () => { describe('countIssues', () => { it('should return a number of total issues by project id', async () => { jest.spyOn(MockIssueService, 'countByProjectId'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await projectController.countIssues(projectId); expect(MockIssueService.countByProjectId).toBeCalledTimes(1); @@ -104,7 +103,7 @@ describe('ProjectController', () => { describe('delete', () => { it('', async () => { jest.spyOn(MockProjectService, 'deleteById'); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await projectController.delete(projectId); expect(MockProjectService.deleteById).toBeCalledTimes(1); diff --git a/apps/api/src/domains/project/project/project.controller.ts b/apps/api/src/domains/project/project/project.controller.ts index 39eca865a..d83c73414 100644 --- a/apps/api/src/domains/project/project/project.controller.ts +++ b/apps/api/src/domains/project/project/project.controller.ts @@ -32,7 +32,6 @@ import { FeedbackService } from '@/domains/feedback/feedback.service'; import { CurrentUser } from '@/domains/user/decorators'; import { UserDto } from '@/domains/user/dtos'; import { SuperUser } from '@/domains/user/super-user.decorator'; - import { IssueService } from '../issue/issue.service'; import { PermissionEnum } from '../role/permission.enum'; import { RequirePermission } from '../role/require-permission.decorator'; diff --git a/apps/api/src/domains/project/project/project.entity.ts b/apps/api/src/domains/project/project/project.entity.ts index fb1f2cb77..a6c1ef855 100644 --- a/apps/api/src/domains/project/project/project.entity.ts +++ b/apps/api/src/domains/project/project/project.entity.ts @@ -23,10 +23,9 @@ import { } from 'typeorm'; import { CommonEntity } from '@/common/entities'; -import { ApiKeyEntity } from '@/domains/project/api-key/api-key.entity'; -import { IssueTrackerEntity } from '@/domains/project/issue-tracker/issue-tracker.entity'; +import type { ApiKeyEntity } from '@/domains/project/api-key/api-key.entity'; +import type { IssueTrackerEntity } from '@/domains/project/issue-tracker/issue-tracker.entity'; import { TenantEntity } from '@/domains/tenant/tenant.entity'; - import { ChannelEntity } from '../../channel/channel/channel.entity'; import { IssueEntity } from '../issue/issue.entity'; import { RoleEntity } from '../role/role.entity'; diff --git a/apps/api/src/domains/project/project/project.module.ts b/apps/api/src/domains/project/project/project.module.ts index f4f1017c8..59b84805b 100644 --- a/apps/api/src/domains/project/project/project.module.ts +++ b/apps/api/src/domains/project/project/project.module.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Module, forwardRef } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpensearchRepository } from '@/common/repositories'; @@ -25,7 +25,6 @@ import { OptionModule } from '@/domains/channel/option/option.module'; import { FeedbackEntity } from '@/domains/feedback/feedback.entity'; import { FeedbackModule } from '@/domains/feedback/feedback.module'; import { TenantModule } from '@/domains/tenant/tenant.module'; - import { IssueEntity } from '../issue/issue.entity'; import { IssueModule } from '../issue/issue.module'; import { RoleModule } from '../role/role.module'; diff --git a/apps/api/src/domains/project/project/project.service.spec.ts b/apps/api/src/domains/project/project/project.service.spec.ts index 9b3891f81..23aaba401 100644 --- a/apps/api/src/domains/project/project/project.service.spec.ts +++ b/apps/api/src/domains/project/project/project.service.spec.ts @@ -16,23 +16,19 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Like, Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; +import { Like } from 'typeorm'; -import { OpensearchRepository } from '@/common/repositories'; import { TenantEntity } from '@/domains/tenant/tenant.entity'; -import { TenantServiceProviders } from '@/domains/tenant/tenant.service.spec'; import { UserDto } from '@/domains/user/dtos'; import { UserTypeEnum } from '@/domains/user/entities/enums'; import { - MockOpensearchRepository, createQueryBuilder, - getMockProvider, - mockRepository, -} from '@/utils/test-utils'; - + MockOpensearchRepository, +} from '@/test-utils/util-functions'; +import { ProjectServiceProviders } from '../../../test-utils/providers/project.service.providers'; import { ChannelEntity } from '../../channel/channel/channel.entity'; import { ProjectEntity } from '../../project/project/project.entity'; -import { RoleServiceProviders } from '../role/role.service.spec'; import { CreateProjectDto, FindAllProjectsDto, UpdateProjectDto } from './dtos'; import { FindByProjectIdDto } from './dtos/find-by-project-id.dto'; import { @@ -42,21 +38,6 @@ import { } from './exceptions'; import { ProjectService } from './project.service'; -export const ProjectServiceProviders = [ - ProjectService, - { - provide: getRepositoryToken(ProjectEntity), - useValue: mockRepository(), - }, - { - provide: getRepositoryToken(ChannelEntity), - useValue: mockRepository(), - }, - getMockProvider(OpensearchRepository, MockOpensearchRepository), - ...TenantServiceProviders, - ...RoleServiceProviders, -]; - describe('ProjectService Test suite', () => { let projectService: ProjectService; let projectRepo: Repository; @@ -74,8 +55,8 @@ describe('ProjectService Test suite', () => { }); describe('create', () => { - const name = faker.datatype.string(); - const description = faker.datatype.string(); + const name = faker.string.sample(); + const description = faker.string.sample(); let dto: CreateProjectDto; beforeEach(() => { dto = new CreateProjectDto(); @@ -84,7 +65,7 @@ describe('ProjectService Test suite', () => { }); it('creating a project succeeds with valid inputs', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); jest.spyOn(projectRepo, 'findOneBy').mockResolvedValue(null); jest.spyOn(tenantRepo, 'find').mockResolvedValue([{}] as TenantEntity[]); jest @@ -99,7 +80,7 @@ describe('ProjectService Test suite', () => { expect(id).toEqual(projectId); }); it('creating a project fails with an existent project name', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); jest .spyOn(projectRepo, 'findOneBy') .mockResolvedValue({ name: dto.name } as ProjectEntity); @@ -126,7 +107,7 @@ describe('ProjectService Test suite', () => { it('finding all projects succeds as a SUPER user', async () => { dto.user = new UserDto(); dto.user.type = UserTypeEnum.SUPER; - dto.searchText = faker.datatype.string(); + dto.searchText = faker.string.sample(); jest .spyOn(projectRepo, 'createQueryBuilder') .mockImplementation(() => createQueryBuilder); @@ -141,11 +122,11 @@ describe('ProjectService Test suite', () => { }); }); it('finding all projects succeds as a GENERAL user', async () => { - const userId = faker.datatype.number(); + const userId = faker.number.int(); dto.user = new UserDto(); dto.user.type = UserTypeEnum.GENERAL; dto.user.id = userId; - dto.searchText = faker.datatype.string(); + dto.searchText = faker.string.sample(); jest .spyOn(projectRepo, 'createQueryBuilder') .mockImplementation(() => createQueryBuilder); @@ -169,7 +150,7 @@ describe('ProjectService Test suite', () => { dto = new FindByProjectIdDto(); }); it('finding a project by an id succeeds with a valid id', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); jest.spyOn(projectRepo, 'findOneBy').mockResolvedValue({ id: projectId, } as ProjectEntity); @@ -192,15 +173,15 @@ describe('ProjectService Test suite', () => { }); }); describe('update ', () => { - const description = faker.datatype.string(); + const description = faker.string.sample(); let dto: UpdateProjectDto; beforeEach(() => { dto = new UpdateProjectDto(); dto.description = description; }); it('updating a project succeeds with valid inputs', async () => { - const projectId = faker.datatype.number(); - const name = faker.datatype.string(); + const projectId = faker.number.int(); + const name = faker.string.sample(); dto.name = name; jest.spyOn(projectRepo, 'findOneBy').mockResolvedValue({ id: projectId, @@ -219,7 +200,7 @@ describe('ProjectService Test suite', () => { expect(projectRepo.save).toBeCalledTimes(1); }); it('updating a project fails with a duplicate name', async () => { - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); const name = 'DUPLICATE_NAME'; dto.name = name; jest.spyOn(projectRepo, 'findOneBy').mockResolvedValue({ @@ -243,11 +224,11 @@ describe('ProjectService Test suite', () => { }); describe('deleteById', () => { it('deleting a project succeeds with a valid id', async () => { - const projectId = faker.datatype.number(); - const channelCount = faker.datatype.number({ min: 1, max: 10 }); + const projectId = faker.number.int(); + const channelCount = faker.number.int({ min: 1, max: 10 }); jest.spyOn(channelRepo, 'find').mockResolvedValue( Array(channelCount).fill({ - id: faker.datatype.number(), + id: faker.number.int(), }) as ChannelEntity[], ); jest.spyOn(projectRepo, 'remove'); diff --git a/apps/api/src/domains/project/project/project.service.ts b/apps/api/src/domains/project/project/project.service.ts index 8c32cb24b..c4cf8aaad 100644 --- a/apps/api/src/domains/project/project/project.service.ts +++ b/apps/api/src/domains/project/project/project.service.ts @@ -23,13 +23,13 @@ import { OpensearchRepository } from '@/common/repositories'; import { OS_USE } from '@/configs/opensearch.config'; import { TenantService } from '@/domains/tenant/tenant.service'; import { UserTypeEnum } from '@/domains/user/entities/enums'; - import { ChannelEntity } from '../../channel/channel/channel.entity'; import { ProjectEntity } from '../../project/project/project.entity'; import { AllPermissionList } from '../role/permission.enum'; import { RoleService } from '../role/role.service'; -import { CreateProjectDto, FindAllProjectsDto, UpdateProjectDto } from './dtos'; -import { FindByProjectIdDto } from './dtos/find-by-project-id.dto'; +import type { FindAllProjectsDto } from './dtos'; +import { CreateProjectDto, UpdateProjectDto } from './dtos'; +import type { FindByProjectIdDto } from './dtos/find-by-project-id.dto'; import { ProjectAlreadyExistsException, ProjectInvalidNameException, diff --git a/apps/api/src/domains/project/role/dtos/create-role.dto.ts b/apps/api/src/domains/project/role/dtos/create-role.dto.ts index d11cb9605..edf690c86 100644 --- a/apps/api/src/domains/project/role/dtos/create-role.dto.ts +++ b/apps/api/src/domains/project/role/dtos/create-role.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { PermissionEnum } from '../permission.enum'; +import type { PermissionEnum } from '../permission.enum'; export class CreateRoleDto { projectId: number; diff --git a/apps/api/src/domains/project/role/dtos/requests/create-role-request.dto.ts b/apps/api/src/domains/project/role/dtos/requests/create-role-request.dto.ts index 403655951..cb9df6b11 100644 --- a/apps/api/src/domains/project/role/dtos/requests/create-role-request.dto.ts +++ b/apps/api/src/domains/project/role/dtos/requests/create-role-request.dto.ts @@ -17,7 +17,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsString } from 'class-validator'; import { ArrayDistinct } from '@/common/validators'; - import { PermissionEnum } from '../../permission.enum'; export class CreateRoleRequestDto { diff --git a/apps/api/src/domains/project/role/dtos/responses/get-all-role-response.dto.ts b/apps/api/src/domains/project/role/dtos/responses/get-all-role-response.dto.ts index 078729fb6..40f629d3b 100644 --- a/apps/api/src/domains/project/role/dtos/responses/get-all-role-response.dto.ts +++ b/apps/api/src/domains/project/role/dtos/responses/get-all-role-response.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PermissionEnum } from '../../permission.enum'; diff --git a/apps/api/src/domains/project/role/permission.guard.ts b/apps/api/src/domains/project/role/permission.guard.ts index b9427ddc2..2b6ee2d39 100644 --- a/apps/api/src/domains/project/role/permission.guard.ts +++ b/apps/api/src/domains/project/role/permission.guard.ts @@ -13,19 +13,14 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { - BadRequestException, - CanActivate, - ExecutionContext, - Injectable, -} from '@nestjs/common'; +import type { CanActivate, ExecutionContext } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; import { UserTypeEnum } from '@/domains/user/entities/enums'; - -import { PermissionEnum } from './permission.enum'; +import type { PermissionEnum } from './permission.enum'; import { PERMISSIONS_KEY } from './require-permission.decorator'; import { RoleEntity } from './role.entity'; diff --git a/apps/api/src/domains/project/role/require-permission.decorator.ts b/apps/api/src/domains/project/role/require-permission.decorator.ts index 862e81928..0be43ee85 100644 --- a/apps/api/src/domains/project/role/require-permission.decorator.ts +++ b/apps/api/src/domains/project/role/require-permission.decorator.ts @@ -13,10 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { SetMetadata, UseGuards, applyDecorators } from '@nestjs/common'; +import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from '../../auth/guards'; -import { PermissionEnum } from './permission.enum'; +import type { PermissionEnum } from './permission.enum'; import { PermissionGuard } from './permission.guard'; export const PERMISSIONS_KEY = 'permissions'; diff --git a/apps/api/src/domains/project/role/role.controller.spec.ts b/apps/api/src/domains/project/role/role.controller.spec.ts index c78007608..b40e866ea 100644 --- a/apps/api/src/domains/project/role/role.controller.spec.ts +++ b/apps/api/src/domains/project/role/role.controller.spec.ts @@ -18,11 +18,10 @@ import { Test } from '@nestjs/testing'; import { DataSource } from 'typeorm'; import { - MockDataSource, getMockProvider, getRandomEnumValue, -} from '@/utils/test-utils'; - + MockDataSource, +} from '@/test-utils/util-functions'; import { CreateRoleDto, UpdateRoleDto } from './dtos'; import { PermissionEnum } from './permission.enum'; import { RoleController } from './role.controller'; @@ -58,16 +57,16 @@ describe('Role Controller', () => { }); it('getAllRolesByProjectId', async () => { - const total = faker.datatype.number({ min: 0, max: 10 }); + const total = faker.number.int({ min: 0, max: 10 }); const roles = Array.from({ length: total }).map(() => ({ - _id: faker.datatype.number(), - id: faker.datatype.number(), - name: faker.datatype.string(), + _id: faker.number.int(), + id: faker.number.int(), + name: faker.string.sample(), permissions: [getRandomEnumValue(PermissionEnum)], - __v: faker.datatype.number({ max: 100, min: 0 }), - [faker.datatype.string()]: faker.datatype.string(), + __v: faker.number.int({ max: 100, min: 0 }), + [faker.string.sample()]: faker.string.sample(), })); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); jest.spyOn(MockRoleService, 'findByProjectId').mockResolvedValue({ roles, total, @@ -88,7 +87,7 @@ describe('Role Controller', () => { it('createRole', async () => { const dto = new CreateRoleDto(); - const projectId = faker.datatype.number(); + const projectId = faker.number.int(); await controller.createRole(projectId, dto); @@ -98,8 +97,8 @@ describe('Role Controller', () => { it('updateRole', async () => { const dto = new UpdateRoleDto(); - const id = faker.datatype.number(); - const projectId = faker.datatype.number(); + const id = faker.number.int(); + const projectId = faker.number.int(); await controller.updateRole(projectId, id, dto); @@ -108,7 +107,7 @@ describe('Role Controller', () => { }); it('deleteRole', async () => { - const id = faker.datatype.number(); + const id = faker.number.int(); await controller.deleteRole(id); diff --git a/apps/api/src/domains/project/role/role.entity.ts b/apps/api/src/domains/project/role/role.entity.ts index 6ad96b763..4718335b3 100644 --- a/apps/api/src/domains/project/role/role.entity.ts +++ b/apps/api/src/domains/project/role/role.entity.ts @@ -16,10 +16,9 @@ import { Column, Entity, Index, ManyToOne, OneToMany, Relation } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { MemberEntity } from '../member/member.entity'; import { ProjectEntity } from '../project/project.entity'; -import { PermissionEnum } from './permission.enum'; +import type { PermissionEnum } from './permission.enum'; @Entity('roles') @Index(['name', 'project']) diff --git a/apps/api/src/domains/project/role/role.service.spec.ts b/apps/api/src/domains/project/role/role.service.spec.ts index 49a137a7d..fd4aa9856 100644 --- a/apps/api/src/domains/project/role/role.service.spec.ts +++ b/apps/api/src/domains/project/role/role.service.spec.ts @@ -16,10 +16,10 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { getRandomEnumValues, mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { getRandomEnumValues } from '@/test-utils/util-functions'; +import { RoleServiceProviders } from '../../../test-utils/providers/role.service.providers'; import { CreateRoleDto, UpdateRoleDto } from './dtos'; import { RoleAlreadyExistsException, @@ -29,14 +29,6 @@ import { PermissionEnum } from './permission.enum'; import { RoleEntity } from './role.entity'; import { RoleService } from './role.service'; -export const RoleServiceProviders = [ - RoleService, - { - provide: getRepositoryToken(RoleEntity), - useValue: mockRepository(), - }, -]; - describe('RoleService', () => { let roleService: RoleService; let roleRepo: Repository; @@ -52,9 +44,9 @@ describe('RoleService', () => { describe('create', () => { it('creating a role succeeds with valid inputs', async () => { const dto = new CreateRoleDto(); - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); dto.permissions = getRandomEnumValues(PermissionEnum); - dto.projectId = faker.datatype.number(); + dto.projectId = faker.number.int(); jest.spyOn(roleRepo, 'findOneBy').mockResolvedValue(null); await roleService.create(dto); @@ -73,12 +65,12 @@ describe('RoleService', () => { }); it('creating a role fails with duplicate inputs', async () => { const dto = new CreateRoleDto(); - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); dto.permissions = getRandomEnumValues(PermissionEnum); - dto.projectId = faker.datatype.number(); + dto.projectId = faker.number.int(); jest .spyOn(roleRepo, 'findOneBy') - .mockResolvedValue({ id: faker.datatype.number() } as RoleEntity); + .mockResolvedValue({ id: faker.number.int() } as RoleEntity); await expect(roleService.create(dto)).rejects.toThrow( RoleAlreadyExistsException, @@ -94,10 +86,10 @@ describe('RoleService', () => { describe('update', () => { it('updating a role succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const projectId = faker.datatype.number(); + const roleId = faker.number.int(); + const projectId = faker.number.int(); const dto = new UpdateRoleDto(); - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); dto.permissions = getRandomEnumValues(PermissionEnum); jest.spyOn(roleRepo, 'findOneBy').mockResolvedValue({ id: roleId, @@ -116,17 +108,17 @@ describe('RoleService', () => { }); }); it('updating a role fails with a duplicate name', async () => { - const roleId = faker.datatype.number(); - const projectId = faker.datatype.number(); + const roleId = faker.number.int(); + const projectId = faker.number.int(); const dto = new UpdateRoleDto(); - dto.name = faker.datatype.string(); + dto.name = faker.string.sample(); dto.permissions = getRandomEnumValues(PermissionEnum); jest.spyOn(roleRepo, 'findOneBy').mockResolvedValue({ id: roleId, name: dto.name, } as RoleEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - id: faker.datatype.number(), + id: faker.number.int(), name: dto.name, } as RoleEntity); @@ -141,7 +133,7 @@ describe('RoleService', () => { describe('findById', () => { it('finding a role succeeds with a valid role id', async () => { - const roleId = faker.datatype.number(); + const roleId = faker.number.int(); jest .spyOn(roleRepo, 'findOne') .mockResolvedValue({ id: roleId } as RoleEntity); @@ -156,7 +148,7 @@ describe('RoleService', () => { expect(result.id).toEqual(roleId); }); it('finding a role fails with a nonexistent role id', async () => { - const nonexistentRoleId = faker.datatype.number(); + const nonexistentRoleId = faker.number.int(); jest.spyOn(roleRepo, 'findOne').mockResolvedValue(null); await expect(roleService.findById(nonexistentRoleId)).rejects.toThrow( @@ -173,9 +165,9 @@ describe('RoleService', () => { describe('findByProjectNameAndRoleName', () => { it('finding a role succeeds with a valid project name and a role name', async () => { - const roleId = faker.datatype.number(); - const projectName = faker.datatype.string(); - const roleName = faker.datatype.string(); + const roleId = faker.number.int(); + const projectName = faker.string.sample(); + const roleName = faker.string.sample(); jest .spyOn(roleRepo, 'findOne') .mockResolvedValue({ id: roleId } as RoleEntity); @@ -193,8 +185,8 @@ describe('RoleService', () => { expect(result.id).toEqual(roleId); }); it('finding a role fails with an invalid project name and an invalid role name', async () => { - const projectName = faker.datatype.string(); - const roleName = faker.datatype.string(); + const projectName = faker.string.sample(); + const roleName = faker.string.sample(); jest.spyOn(roleRepo, 'findOne').mockResolvedValue(null); await expect( diff --git a/apps/api/src/domains/tenant/dtos/responses/get-tenant-response.dto.ts b/apps/api/src/domains/tenant/dtos/responses/get-tenant-response.dto.ts index afdb6532d..666c94b09 100644 --- a/apps/api/src/domains/tenant/dtos/responses/get-tenant-response.dto.ts +++ b/apps/api/src/domains/tenant/dtos/responses/get-tenant-response.dto.ts @@ -14,7 +14,7 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; export class OAuthConfigResponseDto { @Expose() diff --git a/apps/api/src/domains/tenant/dtos/update-tenant.dto.ts b/apps/api/src/domains/tenant/dtos/update-tenant.dto.ts index cf5229e31..388aef043 100644 --- a/apps/api/src/domains/tenant/dtos/update-tenant.dto.ts +++ b/apps/api/src/domains/tenant/dtos/update-tenant.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { OAuthConfig } from '../tenant.entity'; +import type { OAuthConfig } from '../tenant.entity'; export class UpdateTenantDto { siteName: string; diff --git a/apps/api/src/domains/tenant/tenant.entity.ts b/apps/api/src/domains/tenant/tenant.entity.ts index 691d0b864..b8391b8c0 100644 --- a/apps/api/src/domains/tenant/tenant.entity.ts +++ b/apps/api/src/domains/tenant/tenant.entity.ts @@ -13,10 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Column, Entity, OneToMany, Relation } from 'typeorm'; +import type { Relation } from 'typeorm'; +import { Column, Entity, OneToMany } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { ProjectEntity } from '../project/project/project.entity'; export interface OAuthConfig { diff --git a/apps/api/src/domains/tenant/tenant.service.spec.ts b/apps/api/src/domains/tenant/tenant.service.spec.ts index b282c9cda..81bac7a4d 100644 --- a/apps/api/src/domains/tenant/tenant.service.spec.ts +++ b/apps/api/src/domains/tenant/tenant.service.spec.ts @@ -16,10 +16,9 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { TenantServiceProviders } from '../../test-utils/providers/tenant.service.providers'; import { FeedbackEntity } from '../feedback/feedback.entity'; import { UserEntity } from '../user/entities/user.entity'; import { @@ -34,22 +33,6 @@ import { import { TenantEntity } from './tenant.entity'; import { TenantService } from './tenant.service'; -export const TenantServiceProviders = [ - TenantService, - { - provide: getRepositoryToken(TenantEntity), - useValue: mockRepository(), - }, - { - provide: getRepositoryToken(UserEntity), - useValue: mockRepository(), - }, - { - provide: getRepositoryToken(FeedbackEntity), - useValue: mockRepository(), - }, -]; - describe('TenantService', () => { let tenantService: TenantService; let tenantRepo: Repository; @@ -69,7 +52,7 @@ describe('TenantService', () => { describe('create', () => { it('creation succeeds with valid data', async () => { const dto = new SetupTenantDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); jest.spyOn(tenantRepo, 'find').mockResolvedValue([]); await tenantService.create(dto); @@ -81,7 +64,7 @@ describe('TenantService', () => { }); it('creation fails with the duplicate site name', async () => { const dto = new SetupTenantDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); jest.spyOn(tenantRepo, 'find').mockResolvedValue([{} as TenantEntity]); await expect(tenantService.create(dto)).rejects.toThrow( @@ -91,25 +74,25 @@ describe('TenantService', () => { }); describe('update', () => { const dto = new UpdateTenantDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); dto.useEmail = faker.datatype.boolean(); dto.isPrivate = faker.datatype.boolean(); dto.isRestrictDomain = faker.datatype.boolean(); - dto.allowDomains = [faker.datatype.string()]; + dto.allowDomains = [faker.string.sample()]; dto.useOAuth = faker.datatype.boolean(); dto.oauthConfig = { - clientId: faker.datatype.string(), - clientSecret: faker.datatype.string(), - authCodeRequestURL: faker.datatype.string(), - scopeString: faker.datatype.string(), - accessTokenRequestURL: faker.datatype.string(), - userProfileRequestURL: faker.datatype.string(), - emailKey: faker.datatype.string(), + clientId: faker.string.sample(), + clientSecret: faker.string.sample(), + authCodeRequestURL: faker.string.sample(), + scopeString: faker.string.sample(), + accessTokenRequestURL: faker.string.sample(), + userProfileRequestURL: faker.string.sample(), + emailKey: faker.string.sample(), defatulLoginEnable: faker.datatype.boolean(), }; it('update succeeds with valid data', async () => { - const tenantId = faker.datatype.number(); + const tenantId = faker.number.int(); jest .spyOn(tenantRepo, 'find') .mockResolvedValue([{ id: tenantId }] as TenantEntity[]); @@ -129,7 +112,7 @@ describe('TenantService', () => { }); describe('findOne', () => { it('finding a tenant succeeds when there is a tenant', async () => { - const tenantId = faker.datatype.number(); + const tenantId = faker.number.int(); jest .spyOn(tenantRepo, 'find') .mockResolvedValue([{ id: tenantId }] as TenantEntity[]); @@ -149,8 +132,8 @@ describe('TenantService', () => { }); describe('countByTenantId', () => { it('counting feedbacks by tenant id', async () => { - const count = faker.datatype.number(); - const tenantId = faker.datatype.number(); + const count = faker.number.int(); + const tenantId = faker.number.int(); const dto = new FeedbackCountByTenantIdDto(); dto.tenantId = tenantId; jest.spyOn(feedbackRepo, 'count').mockResolvedValue(count); diff --git a/apps/api/src/domains/tenant/tenant.service.ts b/apps/api/src/domains/tenant/tenant.service.ts index ee3d20435..a96934168 100644 --- a/apps/api/src/domains/tenant/tenant.service.ts +++ b/apps/api/src/domains/tenant/tenant.service.ts @@ -19,15 +19,11 @@ import { Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; import { SMTP_USE } from '@/configs/smtp.config'; - import { FeedbackEntity } from '../feedback/feedback.entity'; import { UserTypeEnum } from '../user/entities/enums'; import { UserEntity } from '../user/entities/user.entity'; -import { - FeedbackCountByTenantIdDto, - SetupTenantDto, - UpdateTenantDto, -} from './dtos'; +import type { FeedbackCountByTenantIdDto } from './dtos'; +import { SetupTenantDto, UpdateTenantDto } from './dtos'; import { TenantAlreadyExistsException, TenantNotFoundException, diff --git a/apps/api/src/domains/user/create-user.service.spec.ts b/apps/api/src/domains/user/create-user.service.spec.ts index 3c6a42913..43639f0de 100644 --- a/apps/api/src/domains/user/create-user.service.spec.ts +++ b/apps/api/src/domains/user/create-user.service.spec.ts @@ -16,18 +16,15 @@ import { faker } from '@faker-js/faker'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; - -import { mockRepository } from '@/utils/test-utils'; +import type { Repository } from 'typeorm'; +import { CreateUserServiceProviders } from '../../test-utils/providers/create-user.service.providers'; import { MemberEntity } from '../project/member/member.entity'; -import { MemberServiceProviders } from '../project/member/member.service.spec'; import { RoleEntity } from '../project/role/role.entity'; import { TenantEntity } from '../tenant/tenant.entity'; -import { TenantServiceProviders } from '../tenant/tenant.service.spec'; import { CreateUserService } from './create-user.service'; -import { CreateEmailUserDto, CreateInvitationUserDto } from './dtos'; -import { CreateOAuthUserDto } from './dtos/create-oauth-user.dto'; +import type { CreateEmailUserDto, CreateInvitationUserDto } from './dtos'; +import type { CreateOAuthUserDto } from './dtos/create-oauth-user.dto'; import { SignUpMethodEnum, UserTypeEnum } from './entities/enums'; import { UserEntity } from './entities/user.entity'; import { @@ -35,18 +32,6 @@ import { NotAllowedUserCreateException, UserAlreadyExistsException, } from './exceptions'; -import { UserPasswordServiceProviders } from './user-password.service.spec'; - -export const CreateUserServiceProviders = [ - CreateUserService, - ...UserPasswordServiceProviders, - ...TenantServiceProviders, - ...MemberServiceProviders, - { - provide: getRepositoryToken(UserEntity), - useValue: mockRepository(), - }, -]; describe('CreateUserService', () => { let createUserService: CreateUserService; @@ -216,8 +201,8 @@ describe('CreateUserService', () => { expect(userRepo.save).toHaveBeenCalledTimes(0); }); it('creating a general user having a role by an invitation succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const roleId = faker.number.int(); + const userId = faker.number.int(); const dto: CreateInvitationUserDto = { email: faker.internet.email().split('@')[0] + '@linecorp.com', password: faker.internet.password(), @@ -229,7 +214,7 @@ describe('CreateUserService', () => { .spyOn(userRepo, 'save') .mockResolvedValue({ id: userId } as UserEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - project: { id: faker.datatype.number() }, + project: { id: faker.number.int() }, } as RoleEntity); await createUserService.createInvitationUser(dto); @@ -254,8 +239,8 @@ describe('CreateUserService', () => { }); }); it('creating a super user having a role by an invitation succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const roleId = faker.number.int(); + const userId = faker.number.int(); const dto: CreateInvitationUserDto = { email: faker.internet.email().split('@')[0] + '@linecorp.com', password: faker.internet.password(), @@ -267,7 +252,7 @@ describe('CreateUserService', () => { .spyOn(userRepo, 'save') .mockResolvedValue({ id: userId } as UserEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - project: { id: faker.datatype.number() }, + project: { id: faker.number.int() }, } as RoleEntity); await createUserService.createInvitationUser(dto); @@ -358,8 +343,8 @@ describe('CreateUserService', () => { }); }); it('creating a general user having a role by an invitation succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const roleId = faker.number.int(); + const userId = faker.number.int(); const dto: CreateInvitationUserDto = { email: faker.internet.email(), password: faker.internet.password(), @@ -371,7 +356,7 @@ describe('CreateUserService', () => { .spyOn(userRepo, 'save') .mockResolvedValue({ id: userId } as UserEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - project: { id: faker.datatype.number() }, + project: { id: faker.number.int() }, } as RoleEntity); await createUserService.createInvitationUser(dto); @@ -396,8 +381,8 @@ describe('CreateUserService', () => { }); }); it('creating a super user having a role by an invitation succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const roleId = faker.number.int(); + const userId = faker.number.int(); const dto: CreateInvitationUserDto = { email: faker.internet.email(), password: faker.internet.password(), @@ -409,7 +394,7 @@ describe('CreateUserService', () => { .spyOn(userRepo, 'save') .mockResolvedValue({ id: userId } as UserEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - project: { id: faker.datatype.number() }, + project: { id: faker.number.int() }, } as RoleEntity); await createUserService.createInvitationUser(dto); @@ -556,8 +541,8 @@ describe('CreateUserService', () => { expect(userRepo.save).toHaveBeenCalledTimes(0); }); it('creating a general user having a role by an invitation succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const roleId = faker.number.int(); + const userId = faker.number.int(); const dto: CreateInvitationUserDto = { email: faker.internet.email().split('@')[0] + '@linecorp.com', password: faker.internet.password(), @@ -569,7 +554,7 @@ describe('CreateUserService', () => { .spyOn(userRepo, 'save') .mockResolvedValue({ id: userId } as UserEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - project: { id: faker.datatype.number() }, + project: { id: faker.number.int() }, } as RoleEntity); await createUserService.createInvitationUser(dto); @@ -594,8 +579,8 @@ describe('CreateUserService', () => { }); }); it('creating a super user having a role by an invitation succeeds with valid inputs', async () => { - const roleId = faker.datatype.number(); - const userId = faker.datatype.number(); + const roleId = faker.number.int(); + const userId = faker.number.int(); const dto: CreateInvitationUserDto = { email: faker.internet.email().split('@')[0] + '@linecorp.com', password: faker.internet.password(), @@ -607,7 +592,7 @@ describe('CreateUserService', () => { .spyOn(userRepo, 'save') .mockResolvedValue({ id: userId } as UserEntity); jest.spyOn(roleRepo, 'findOne').mockResolvedValue({ - project: { id: faker.datatype.number() }, + project: { id: faker.number.int() }, } as RoleEntity); await createUserService.createInvitationUser(dto); diff --git a/apps/api/src/domains/user/create-user.service.ts b/apps/api/src/domains/user/create-user.service.ts index d8cace12a..5dfc64ee8 100644 --- a/apps/api/src/domains/user/create-user.service.ts +++ b/apps/api/src/domains/user/create-user.service.ts @@ -20,8 +20,8 @@ import { Transactional } from 'typeorm-transactional'; import { MemberService } from '../project/member/member.service'; import { TenantService } from '../tenant/tenant.service'; -import { CreateEmailUserDto, CreateInvitationUserDto } from './dtos'; -import { CreateOAuthUserDto } from './dtos/create-oauth-user.dto'; +import type { CreateEmailUserDto, CreateInvitationUserDto } from './dtos'; +import type { CreateOAuthUserDto } from './dtos/create-oauth-user.dto'; import { CreateUserDto } from './dtos/create-user.dto'; import { SignUpMethodEnum } from './entities/enums'; import { UserEntity } from './entities/user.entity'; diff --git a/apps/api/src/domains/user/decorators/current-user.decorator.ts b/apps/api/src/domains/user/decorators/current-user.decorator.ts index c9019151a..011366316 100644 --- a/apps/api/src/domains/user/decorators/current-user.decorator.ts +++ b/apps/api/src/domains/user/decorators/current-user.decorator.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { ExecutionContext, createParamDecorator } from '@nestjs/common'; +import type { ExecutionContext } from '@nestjs/common'; +import { createParamDecorator } from '@nestjs/common'; type DataType = 'id'; diff --git a/apps/api/src/domains/user/decorators/is-nullable.decorator.ts b/apps/api/src/domains/user/decorators/is-nullable.decorator.ts index 903f8623a..67a706db4 100644 --- a/apps/api/src/domains/user/decorators/is-nullable.decorator.ts +++ b/apps/api/src/domains/user/decorators/is-nullable.decorator.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { ValidateIf, ValidationOptions } from 'class-validator'; +import type { ValidationOptions } from 'class-validator'; +import { ValidateIf } from 'class-validator'; export function IsNullable(validationOptions?: ValidationOptions) { return ValidateIf((_object, value) => value !== null, validationOptions); diff --git a/apps/api/src/domains/user/dtos/create-invitation-user.dto.ts b/apps/api/src/domains/user/dtos/create-invitation-user.dto.ts index 34d880141..ff66f1776 100644 --- a/apps/api/src/domains/user/dtos/create-invitation-user.dto.ts +++ b/apps/api/src/domains/user/dtos/create-invitation-user.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { UserTypeEnum } from '../entities/enums'; +import type { UserTypeEnum } from '../entities/enums'; export class CreateInvitationUserDto { email: string; diff --git a/apps/api/src/domains/user/dtos/create-user.dto.ts b/apps/api/src/domains/user/dtos/create-user.dto.ts index 0278dca2b..1150896bf 100644 --- a/apps/api/src/domains/user/dtos/create-user.dto.ts +++ b/apps/api/src/domains/user/dtos/create-user.dto.ts @@ -13,10 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { SignUpMethodEnum } from '../entities/enums'; -import { CreateEmailUserDto } from './create-email-user.dto'; -import { CreateInvitationUserDto } from './create-invitation-user.dto'; -import { CreateOAuthUserDto } from './create-oauth-user.dto'; +import type { SignUpMethodEnum } from '../entities/enums'; +import type { CreateEmailUserDto } from './create-email-user.dto'; +import type { CreateInvitationUserDto } from './create-invitation-user.dto'; +import type { CreateOAuthUserDto } from './create-oauth-user.dto'; export type CreateUserDto = ( | Omit diff --git a/apps/api/src/domains/user/dtos/find-all-users.dto.ts b/apps/api/src/domains/user/dtos/find-all-users.dto.ts index 06942e739..6b41796c1 100644 --- a/apps/api/src/domains/user/dtos/find-all-users.dto.ts +++ b/apps/api/src/domains/user/dtos/find-all-users.dto.ts @@ -13,11 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { IPaginationOptions } from 'nestjs-typeorm-paginate'; +import type { IPaginationOptions } from 'nestjs-typeorm-paginate'; -import { SortMethodEnum } from '@/common/enums'; - -import { UserTypeEnum } from '../entities/enums'; +import type { SortMethodEnum } from '@/common/enums'; +import type { UserTypeEnum } from '../entities/enums'; export class FindAllUsersDto { options: IPaginationOptions; diff --git a/apps/api/src/domains/user/dtos/invite-user.dto.ts b/apps/api/src/domains/user/dtos/invite-user.dto.ts index 30e2a84d3..cfae825ab 100644 --- a/apps/api/src/domains/user/dtos/invite-user.dto.ts +++ b/apps/api/src/domains/user/dtos/invite-user.dto.ts @@ -13,8 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { UserTypeEnum } from '../entities/enums'; -import { UserDto } from './user.dto'; +import type { UserTypeEnum } from '../entities/enums'; +import type { UserDto } from './user.dto'; export class InviteUserDto { email: string; diff --git a/apps/api/src/domains/user/dtos/requests/get-all-users-request.dto.ts b/apps/api/src/domains/user/dtos/requests/get-all-users-request.dto.ts index 108f66890..e07410fc0 100644 --- a/apps/api/src/domains/user/dtos/requests/get-all-users-request.dto.ts +++ b/apps/api/src/domains/user/dtos/requests/get-all-users-request.dto.ts @@ -18,7 +18,6 @@ import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; import { PaginationRequestDto, TimeRange } from '@/common/dtos'; import { SortMethodEnum } from '@/common/enums'; - import { UserTypeEnum } from '../../entities/enums'; class UserOrder { diff --git a/apps/api/src/domains/user/dtos/responses/get-all-user-response.dto.ts b/apps/api/src/domains/user/dtos/responses/get-all-user-response.dto.ts index 217e304dc..1034eca6d 100644 --- a/apps/api/src/domains/user/dtos/responses/get-all-user-response.dto.ts +++ b/apps/api/src/domains/user/dtos/responses/get-all-user-response.dto.ts @@ -14,10 +14,9 @@ * under the License. */ import { ApiProperty } from '@nestjs/swagger'; -import { Expose, Type, plainToInstance } from 'class-transformer'; +import { Expose, plainToInstance, Type } from 'class-transformer'; import { PaginationResponseDto } from '@/common/dtos/pagination-response.dto'; - import { SignUpMethodEnum, UserTypeEnum } from '../../entities/enums'; class ProjectDto { diff --git a/apps/api/src/domains/user/dtos/update-user.dto.ts b/apps/api/src/domains/user/dtos/update-user.dto.ts index d72d1be64..d0026535f 100644 --- a/apps/api/src/domains/user/dtos/update-user.dto.ts +++ b/apps/api/src/domains/user/dtos/update-user.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { UserTypeEnum } from '../entities/enums'; +import type { UserTypeEnum } from '../entities/enums'; export class UpdateUserDto { userId: number; diff --git a/apps/api/src/domains/user/entities/user.entity.ts b/apps/api/src/domains/user/entities/user.entity.ts index 229ee5ff0..bf6df6120 100644 --- a/apps/api/src/domains/user/entities/user.entity.ts +++ b/apps/api/src/domains/user/entities/user.entity.ts @@ -13,11 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Column, Entity, OneToMany, Relation, Unique } from 'typeorm'; +import type { Relation } from 'typeorm'; +import { Column, Entity, OneToMany, Unique } from 'typeorm'; import { CommonEntity } from '@/common/entities'; import { MemberEntity } from '@/domains/project/member/member.entity'; - import { SignUpMethodEnum, UserStateEnum, UserTypeEnum } from './enums'; @Entity('users') diff --git a/apps/api/src/domains/user/super-user.decorator.ts b/apps/api/src/domains/user/super-user.decorator.ts index 810284e2e..f729634cc 100644 --- a/apps/api/src/domains/user/super-user.decorator.ts +++ b/apps/api/src/domains/user/super-user.decorator.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { UseGuards, applyDecorators } from '@nestjs/common'; +import { applyDecorators, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from '../auth/guards'; import { SuperUserGuard } from './super-user.guard'; diff --git a/apps/api/src/domains/user/super-user.guard.ts b/apps/api/src/domains/user/super-user.guard.ts index 00299adb2..66761be2f 100644 --- a/apps/api/src/domains/user/super-user.guard.ts +++ b/apps/api/src/domains/user/super-user.guard.ts @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import type { CanActivate, ExecutionContext } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { UserTypeEnum } from './entities/enums'; diff --git a/apps/api/src/domains/user/user-password.service.spec.ts b/apps/api/src/domains/user/user-password.service.spec.ts index d9d3c1ab7..0836a246f 100644 --- a/apps/api/src/domains/user/user-password.service.spec.ts +++ b/apps/api/src/domains/user/user-password.service.spec.ts @@ -14,41 +14,20 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { MailerService } from '@nestjs-modules/mailer'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import * as bcrypt from 'bcrypt'; -import { Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { CodeTypeEnum } from '@/shared/code/code-type.enum'; import { CodeEntity } from '@/shared/code/code.entity'; -import { CodeServiceProviders } from '@/shared/code/code.service.spec'; import { ResetPasswordMailingService } from '@/shared/mailing/reset-password-mailing.service'; -import { getMockProvider, mockRepository } from '@/utils/test-utils'; - +import { UserPasswordServiceProviders } from '../../test-utils/providers/user-password.service.providers'; import { ChangePasswordDto, ResetPasswordDto } from './dtos'; import { UserEntity } from './entities/user.entity'; import { InvalidPasswordException, UserNotFoundException } from './exceptions'; import { UserPasswordService } from './user-password.service'; -const MockMailerService = { - sendMail: jest.fn(), -}; -const MockResetPasswordMailingService = { - send: jest.fn(), -}; - -export const UserPasswordServiceProviders = [ - UserPasswordService, - ...CodeServiceProviders, - getMockProvider(ResetPasswordMailingService, MockResetPasswordMailingService), - getMockProvider(MailerService, MockMailerService), - { - provide: getRepositoryToken(UserEntity), - useValue: mockRepository(), - }, -]; - describe('UserPasswordService', () => { let userPasswordService: UserPasswordService; let resetPasswordMailingService: ResetPasswordMailingService; @@ -67,7 +46,7 @@ describe('UserPasswordService', () => { describe('sendResetPasswordMail', () => { it('sending a reset password mail succeeds with valid inputs', async () => { - const userId = faker.datatype.number(); + const userId = faker.number.int(); const email = faker.internet.email(); jest .spyOn(userRepo, 'findOneBy') @@ -96,9 +75,9 @@ describe('UserPasswordService', () => { it('resetting a password succeeds with valid inputs', async () => { const dto = new ResetPasswordDto(); dto.email = faker.internet.email(); - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); dto.password = faker.internet.password(); - const userId = faker.datatype.number(); + const userId = faker.number.int(); jest .spyOn(userRepo, 'findOneBy') .mockResolvedValue({ id: userId } as UserEntity); @@ -124,7 +103,7 @@ describe('UserPasswordService', () => { it('resetting a password fails with an invalid email', async () => { const dto = new ResetPasswordDto(); dto.email = faker.internet.email(); - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); dto.password = faker.internet.password(); jest.spyOn(userRepo, 'findOneBy').mockResolvedValue(null as UserEntity); @@ -139,7 +118,7 @@ describe('UserPasswordService', () => { describe('changePassword', () => { it('changing the password succeeds with valid inputs', async () => { const dto = new ChangePasswordDto(); - dto.userId = faker.datatype.number(); + dto.userId = faker.number.int(); dto.password = faker.internet.password(); dto.newPassword = faker.internet.password(); jest.spyOn(userRepo, 'findOneBy').mockResolvedValue({ @@ -161,7 +140,7 @@ describe('UserPasswordService', () => { }); it('changing the password fails with the invalid original password', async () => { const dto = new ChangePasswordDto(); - dto.userId = faker.datatype.number(); + dto.userId = faker.number.int(); dto.password = faker.internet.password(); dto.newPassword = faker.internet.password(); jest.spyOn(userRepo, 'findOneBy').mockResolvedValue({ diff --git a/apps/api/src/domains/user/user-password.service.ts b/apps/api/src/domains/user/user-password.service.ts index 458f91fd7..8727618d3 100644 --- a/apps/api/src/domains/user/user-password.service.ts +++ b/apps/api/src/domains/user/user-password.service.ts @@ -22,7 +22,6 @@ import { Transactional } from 'typeorm-transactional'; import { CodeTypeEnum } from '@/shared/code/code-type.enum'; import { CodeService } from '@/shared/code/code.service'; import { ResetPasswordMailingService } from '@/shared/mailing/reset-password-mailing.service'; - import { ChangePasswordDto, ResetPasswordDto } from './dtos'; import { UserEntity } from './entities/user.entity'; import { InvalidPasswordException, UserNotFoundException } from './exceptions'; diff --git a/apps/api/src/domains/user/user.controller.spec.ts b/apps/api/src/domains/user/user.controller.spec.ts index de10fadd3..12df1e54e 100644 --- a/apps/api/src/domains/user/user.controller.spec.ts +++ b/apps/api/src/domains/user/user.controller.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { UnauthorizedException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; -import { getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider } from '@/test-utils/util-functions'; import { UserDto } from './dtos'; import { ChangePasswordRequestDto, @@ -70,7 +69,9 @@ describe('user controller', () => { expect(MockUserService.deleteUsers).toHaveBeenCalledTimes(1); }); it('inviteUser', () => { - userController.inviteUser(new UserInvitationRequestDto()); + const userDto = new UserDto(); + userDto.id = faker.datatype.number(); + userController.inviteUser(new UserInvitationRequestDto(), userDto); expect(MockUserService.sendInvitationCode).toHaveBeenCalledTimes(1); }); it('requestResetPassword', () => { @@ -93,7 +94,7 @@ describe('user controller', () => { it('getUser', () => { const userDto = new UserDto(); - userDto.id = faker.datatype.number(); + userDto.id = faker.number.int(); userController.getUser(userDto.id, userDto); @@ -104,7 +105,7 @@ describe('user controller', () => { describe('deleteUser', () => { it('positive', () => { const userDto = new UserDto(); - userDto.id = faker.datatype.number(); + userDto.id = faker.number.int(); userController.deleteUser(userDto.id, userDto); @@ -113,10 +114,10 @@ describe('user controller', () => { }); it('Unauthorization', () => { const userDto = new UserDto(); - userDto.id = faker.datatype.number(); + userDto.id = faker.number.int(); expect( - userController.deleteUser(faker.datatype.number(), userDto), + userController.deleteUser(faker.number.int(), userDto), ).rejects.toThrow(UnauthorizedException); }); }); diff --git a/apps/api/src/domains/user/user.controller.ts b/apps/api/src/domains/user/user.controller.ts index 6fd73fdf8..87c28db7f 100644 --- a/apps/api/src/domains/user/user.controller.ts +++ b/apps/api/src/domains/user/user.controller.ts @@ -31,7 +31,6 @@ import { import { ApiBody, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { PaginationRequestDto } from '@/common/dtos'; - import { JwtAuthGuard } from '../auth/guards'; import { CurrentUser } from './decorators'; import { UserDto } from './dtos'; diff --git a/apps/api/src/domains/user/user.module.ts b/apps/api/src/domains/user/user.module.ts index 0055ef12e..b0a40c382 100644 --- a/apps/api/src/domains/user/user.module.ts +++ b/apps/api/src/domains/user/user.module.ts @@ -18,7 +18,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CodeModule } from '@/shared/code/code.module'; import { MailingModule } from '@/shared/mailing/mailing.module'; - import { MemberModule } from '../project/member/member.module'; import { TenantModule } from '../tenant/tenant.module'; import { CreateUserService } from './create-user.service'; diff --git a/apps/api/src/domains/user/user.service.spec.ts b/apps/api/src/domains/user/user.service.spec.ts index da66ab8ad..edf536786 100644 --- a/apps/api/src/domains/user/user.service.spec.ts +++ b/apps/api/src/domains/user/user.service.spec.ts @@ -14,22 +14,20 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { MailerService } from '@nestjs-modules/mailer'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { Like, Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; +import { Like } from 'typeorm'; import { SortMethodEnum } from '@/common/enums'; -import { CodeServiceProviders } from '@/shared/code/code.service.spec'; -import { UserInvitationMailingService } from '@/shared/mailing/user-invitation-mailing.service'; import { createQueryBuilder, - getMockProvider, getRandomEnumValue, - mockRepository, -} from '@/utils/test-utils'; - -import { TenantServiceProviders } from '../tenant/tenant.service.spec'; +} from '@/test-utils/util-functions'; +import { + MockUserInvitationMailingService, + UserServiceProviders, +} from '../../test-utils/providers/user.service.providers'; import { FindAllUsersDto, UserDto } from './dtos'; import { SignUpMethodEnum, UserTypeEnum } from './entities/enums'; import { UserEntity } from './entities/user.entity'; @@ -39,27 +37,9 @@ import { } from './exceptions'; import { UserService } from './user.service'; -const MockUserInvitationMailingService = { - send: jest.fn(), -}; const MockCodeService = { setCode: jest.fn(), }; -const MockMailerService = { - sendMail: jest.fn(), -}; - -export const UserServiceProviders = [ - UserService, - { provide: getRepositoryToken(UserEntity), useValue: mockRepository() }, - getMockProvider( - UserInvitationMailingService, - MockUserInvitationMailingService, - ), - getMockProvider(MailerService, MockMailerService), - ...CodeServiceProviders, - ...TenantServiceProviders, -]; describe('UserService', () => { let userService: UserService; @@ -77,14 +57,14 @@ describe('UserService', () => { it('finding succeeds with valid inputs', async () => { const dto = new FindAllUsersDto(); dto.options = { - limit: faker.datatype.number({ min: 10, max: 20 }), - page: faker.datatype.number({ min: 1, max: 2 }), + limit: faker.number.int({ min: 10, max: 20 }), + page: faker.number.int({ min: 1, max: 2 }), }; dto.order = { createdAt: SortMethodEnum.DESC, }; dto.query = { - projectId: faker.datatype.number(), + projectId: faker.number.int(), email: faker.internet.email(), }; jest @@ -130,7 +110,7 @@ describe('UserService', () => { }); describe('findById', () => { it('finding by an id succeeds with an existent id', async () => { - const userId = faker.datatype.number(); + const userId = faker.number.int(); jest .spyOn(userRepo, 'findOne') .mockResolvedValue({ id: userId } as UserEntity); @@ -142,7 +122,7 @@ describe('UserService', () => { expect(result).toMatchObject({ id: userId }); }); it('finding by an id fails with a nonexistent id', async () => { - const userId = faker.datatype.number(); + const userId = faker.number.int(); jest.spyOn(userRepo, 'findOne').mockResolvedValue(null as UserEntity); await expect(userService.findById(userId)).rejects.toThrow( @@ -155,7 +135,7 @@ describe('UserService', () => { }); describe('sendInvitationCode', () => { it('sending an invatiation code fails with an existent user', async () => { - const userId = faker.datatype.number(); + const userId = faker.number.int(); const email = faker.internet.email(); const userType = getRandomEnumValue(UserTypeEnum); jest @@ -165,7 +145,7 @@ describe('UserService', () => { await expect( userService.sendInvitationCode({ email, - roleId: faker.datatype.number(), + roleId: faker.number.int(), userType, invitedBy: new UserDto(), }), diff --git a/apps/api/src/domains/user/user.service.ts b/apps/api/src/domains/user/user.service.ts index 792acd740..a2a9f6c4d 100644 --- a/apps/api/src/domains/user/user.service.ts +++ b/apps/api/src/domains/user/user.service.ts @@ -20,13 +20,13 @@ import { In, Like, Raw, Repository } from 'typeorm'; import { Transactional } from 'typeorm-transactional'; import { UserInvitationMailingService } from '@/shared/mailing/user-invitation-mailing.service'; - import { CodeTypeEnum } from '../../shared/code/code-type.enum'; import { CodeService } from '../../shared/code/code.service'; import { TenantService } from '../tenant/tenant.service'; -import { FindAllUsersDto, InviteUserDto } from './dtos'; +import type { FindAllUsersDto } from './dtos'; +import { InviteUserDto } from './dtos'; import { UpdateUserDto } from './dtos/update-user.dto'; -import { SignUpMethodEnum } from './entities/enums'; +import type { SignUpMethodEnum } from './entities/enums'; import { UserEntity } from './entities/user.entity'; import { NotAllowedDomainException, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index bca92b3c5..5ca15ce7b 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -16,17 +16,15 @@ import { Logger as DefaultLogger, ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; -import { - FastifyAdapter, - NestFastifyApplication, -} from '@nestjs/platform-fastify'; +import type { NestFastifyApplication } from '@nestjs/platform-fastify'; +import { FastifyAdapter } from '@nestjs/platform-fastify'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { Logger } from 'nestjs-pino'; import { initializeTransactionalContext } from 'typeorm-transactional'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './common/filters'; -import { ConfigServiceType } from './types/config-service.type'; +import type { ConfigServiceType } from './types/config-service.type'; const globalPrefix = 'api'; @@ -64,4 +62,4 @@ async function bootstrap() { DefaultLogger.log(`🚀 Application is running on: ${await app.getUrl()}`); } -bootstrap(); +void bootstrap(); diff --git a/apps/api/src/shared/code/code.entity.ts b/apps/api/src/shared/code/code.entity.ts index 18b3415a1..d9850e73d 100644 --- a/apps/api/src/shared/code/code.entity.ts +++ b/apps/api/src/shared/code/code.entity.ts @@ -16,7 +16,6 @@ import { Column, Entity } from 'typeorm'; import { CommonEntity } from '@/common/entities'; - import { CodeTypeEnum } from './code-type.enum'; @Entity('codes') diff --git a/apps/api/src/shared/code/code.module.ts b/apps/api/src/shared/code/code.module.ts index 91c55e0b7..5dab6e974 100644 --- a/apps/api/src/shared/code/code.module.ts +++ b/apps/api/src/shared/code/code.module.ts @@ -17,7 +17,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { MailingModule } from '@/shared/mailing/mailing.module'; - import { CodeEntity } from './code.entity'; import { CodeService } from './code.service'; diff --git a/apps/api/src/shared/code/code.service.spec.ts b/apps/api/src/shared/code/code.service.spec.ts index 999b79636..fba1df30f 100644 --- a/apps/api/src/shared/code/code.service.spec.ts +++ b/apps/api/src/shared/code/code.service.spec.ts @@ -19,12 +19,11 @@ import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import dayjs from 'dayjs'; import MockDate from 'mockdate'; -import { Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { UserDto } from '@/domains/user/dtos'; import { UserTypeEnum } from '@/domains/user/entities/enums'; -import { mockRepository } from '@/utils/test-utils'; - +import { CodeServiceProviders } from '../../test-utils/providers/code.service.providers'; import { CodeTypeEnum } from './code-type.enum'; import { CodeEntity } from './code.entity'; import { CodeService } from './code.service'; @@ -34,11 +33,6 @@ import { SetCodeUserInvitationDto, } from './dtos/set-code.dto'; -export const CodeServiceProviders = [ - CodeService, - { provide: getRepositoryToken(CodeEntity), useValue: mockRepository() }, -]; - describe('CodeService', () => { let codeService: CodeService; let codeRepo: Repository; @@ -54,7 +48,7 @@ describe('CodeService', () => { describe('setCode', () => { it('set email verification type', async () => { const dto = new SetCodeEmailVerificationDto(); - dto.key = faker.datatype.string(); + dto.key = faker.string.sample(); dto.type = CodeTypeEnum.EMAIL_VEIRIFICATION; const code = await codeService.setCode(dto); @@ -72,7 +66,7 @@ describe('CodeService', () => { }); it('set reset password type', async () => { const dto = new SetCodeResetPasswordDto(); - dto.key = faker.datatype.string(); + dto.key = faker.string.sample(); dto.type = CodeTypeEnum.RESET_PASSWORD; const code = await codeService.setCode(dto); @@ -90,10 +84,10 @@ describe('CodeService', () => { }); it('set user invitation type with SUPER user type', async () => { const dto = new SetCodeUserInvitationDto(); - dto.key = faker.datatype.string(); + dto.key = faker.string.sample(); dto.type = CodeTypeEnum.USER_INVITATION; dto.data = { - roleId: faker.datatype.number(), + roleId: faker.number.int(), userType: UserTypeEnum.SUPER, invitedBy: new UserDto(), }; @@ -113,10 +107,10 @@ describe('CodeService', () => { }); it('set user invitation type with GENERAL user type', async () => { const dto = new SetCodeUserInvitationDto(); - dto.key = faker.datatype.string(); + dto.key = faker.string.sample(); dto.type = CodeTypeEnum.USER_INVITATION; dto.data = { - roleId: faker.datatype.number(), + roleId: faker.number.int(), userType: UserTypeEnum.GENERAL, invitedBy: new UserDto(), }; @@ -137,13 +131,13 @@ describe('CodeService', () => { }); describe('verifyCode', () => { const codeEntity: CodeEntity = new CodeEntity(); - const key = faker.datatype.string(); + const key = faker.string.sample(); beforeEach(async () => { - codeEntity.code = faker.datatype.string(6); + codeEntity.code = faker.string.sample(6); codeEntity.key = key; codeEntity.type = CodeTypeEnum.EMAIL_VEIRIFICATION; codeEntity.isVerified = false; - codeEntity.id = faker.datatype.number(); + codeEntity.id = faker.number.int(); codeEntity.expiredAt = dayjs().add(5, 'minutes').toDate(); }); it('verify code with valid code, key, type', async () => { @@ -162,7 +156,7 @@ describe('CodeService', () => { }); it('verify code with invalid code', async () => { const { type } = codeEntity; - const invalidCode = faker.datatype.string(6); + const invalidCode = faker.string.sample(6); jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(codeEntity); await expect( @@ -175,7 +169,7 @@ describe('CodeService', () => { }); it('verify code with invalid key', async () => { const { code, type } = codeEntity; - const invalidKey = faker.datatype.string(6); + const invalidKey = faker.string.sample(6); jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(null); await expect( diff --git a/apps/api/src/shared/code/code.service.ts b/apps/api/src/shared/code/code.service.ts index 0361290e2..65eb33fcd 100644 --- a/apps/api/src/shared/code/code.service.ts +++ b/apps/api/src/shared/code/code.service.ts @@ -26,7 +26,7 @@ import { Transactional } from 'typeorm-transactional'; import { CodeTypeEnum } from './code-type.enum'; import { CodeEntity } from './code.entity'; import { SetCodeDto, VerifyCodeDto } from './dtos'; -import { SetCodeUserInvitationDataDto } from './dtos/set-code.dto'; +import type { SetCodeUserInvitationDataDto } from './dtos/set-code.dto'; export const SECONDS = 60 * 5; diff --git a/apps/api/src/shared/code/dtos/set-code-verified.dto.ts b/apps/api/src/shared/code/dtos/set-code-verified.dto.ts index f866a8608..e40472ae1 100644 --- a/apps/api/src/shared/code/dtos/set-code-verified.dto.ts +++ b/apps/api/src/shared/code/dtos/set-code-verified.dto.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { CodeTypeEnum } from '../code-type.enum'; +import type { CodeTypeEnum } from '../code-type.enum'; export class VerifyCodeDto { type: CodeTypeEnum; diff --git a/apps/api/src/shared/code/dtos/set-code.dto.ts b/apps/api/src/shared/code/dtos/set-code.dto.ts index e04b14ef4..0810141b5 100644 --- a/apps/api/src/shared/code/dtos/set-code.dto.ts +++ b/apps/api/src/shared/code/dtos/set-code.dto.ts @@ -13,10 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { UserDto } from '@/domains/user/dtos'; -import { UserTypeEnum } from '@/domains/user/entities/enums'; - -import { CodeTypeEnum } from '../code-type.enum'; +import type { UserDto } from '@/domains/user/dtos'; +import type { UserTypeEnum } from '@/domains/user/entities/enums'; +import type { CodeTypeEnum } from '../code-type.enum'; export type SetCodeDto = | SetCodeEmailVerificationDto diff --git a/apps/api/src/shared/mailing/email-verification-mailing.service.spec.ts b/apps/api/src/shared/mailing/email-verification-mailing.service.spec.ts index cef08c1e3..02d12db16 100644 --- a/apps/api/src/shared/mailing/email-verification-mailing.service.spec.ts +++ b/apps/api/src/shared/mailing/email-verification-mailing.service.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { MailerService } from '@nestjs-modules/mailer'; import { Test } from '@nestjs/testing'; -import { getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider } from '@/test-utils/util-functions'; import { EmailVerificationMailingService } from './email-verification-mailing.service'; describe('first', () => { @@ -38,7 +37,7 @@ describe('first', () => { expect(emailVerificationMailingService).toBeDefined(); }); it('send', async () => { - const code = faker.datatype.string(); + const code = faker.string.sample(); const email = faker.internet.email(); await emailVerificationMailingService.send({ code, email }); expect(MockMailerService.sendMail).toHaveBeenCalledTimes(1); diff --git a/apps/api/src/shared/mailing/email-verification-mailing.service.ts b/apps/api/src/shared/mailing/email-verification-mailing.service.ts index f1dc6b7e9..fcd83cccc 100644 --- a/apps/api/src/shared/mailing/email-verification-mailing.service.ts +++ b/apps/api/src/shared/mailing/email-verification-mailing.service.ts @@ -16,7 +16,7 @@ import { MailerService } from '@nestjs-modules/mailer'; import { Injectable } from '@nestjs/common'; -import { SendMailDto } from './send-mail.dto'; +import type { SendMailDto } from './send-mail.dto'; @Injectable() export class EmailVerificationMailingService { diff --git a/apps/api/src/shared/mailing/mailing.module.ts b/apps/api/src/shared/mailing/mailing.module.ts index 236b0b39b..e4b2c9b43 100644 --- a/apps/api/src/shared/mailing/mailing.module.ts +++ b/apps/api/src/shared/mailing/mailing.module.ts @@ -16,7 +16,6 @@ import { Module } from '@nestjs/common'; import { MailerConfigModule } from '@/configs/modules'; - import { EmailVerificationMailingService } from './email-verification-mailing.service'; import { ResetPasswordMailingService } from './reset-password-mailing.service'; import { UserInvitationMailingService } from './user-invitation-mailing.service'; diff --git a/apps/api/src/shared/mailing/reset-password-mailing.service.spec.ts b/apps/api/src/shared/mailing/reset-password-mailing.service.spec.ts index 1bf1c3c3c..8aa7329c6 100644 --- a/apps/api/src/shared/mailing/reset-password-mailing.service.spec.ts +++ b/apps/api/src/shared/mailing/reset-password-mailing.service.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { MailerService } from '@nestjs-modules/mailer'; import { Test } from '@nestjs/testing'; -import { TestConfig, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, TestConfig } from '@/test-utils/util-functions'; import { ResetPasswordMailingService } from './reset-password-mailing.service'; describe('ResetPasswordMailingService', () => { @@ -37,7 +36,7 @@ describe('ResetPasswordMailingService', () => { expect(resetPasswordMailingService).toBeDefined(); }); it('sends a mail', async () => { - const code = faker.datatype.string(); + const code = faker.string.sample(); const email = faker.internet.email(); await resetPasswordMailingService.send({ code, email }); diff --git a/apps/api/src/shared/mailing/reset-password-mailing.service.ts b/apps/api/src/shared/mailing/reset-password-mailing.service.ts index 7500152df..bc46c0acf 100644 --- a/apps/api/src/shared/mailing/reset-password-mailing.service.ts +++ b/apps/api/src/shared/mailing/reset-password-mailing.service.ts @@ -17,9 +17,8 @@ import { MailerService } from '@nestjs-modules/mailer'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { ConfigServiceType } from '@/types/config-service.type'; - -import { SendMailDto } from './send-mail.dto'; +import type { ConfigServiceType } from '@/types/config-service.type'; +import type { SendMailDto } from './send-mail.dto'; @Injectable() export class ResetPasswordMailingService { diff --git a/apps/api/src/shared/mailing/user-invitation-mailing.service.spec.ts b/apps/api/src/shared/mailing/user-invitation-mailing.service.spec.ts index 3992bd7db..71da45b12 100644 --- a/apps/api/src/shared/mailing/user-invitation-mailing.service.spec.ts +++ b/apps/api/src/shared/mailing/user-invitation-mailing.service.spec.ts @@ -17,8 +17,7 @@ import { faker } from '@faker-js/faker'; import { MailerService } from '@nestjs-modules/mailer'; import { Test } from '@nestjs/testing'; -import { TestConfig, getMockProvider } from '@/utils/test-utils'; - +import { getMockProvider, TestConfig } from '@/test-utils/util-functions'; import { UserInvitationMailingService } from './user-invitation-mailing.service'; describe('first', () => { @@ -39,7 +38,7 @@ describe('first', () => { }); it('send', async () => { - const code = faker.datatype.string(); + const code = faker.string.sample(); const email = faker.internet.email(); await userInvitationMailingService.send({ code, email }); expect(MockMailerService.sendMail).toHaveBeenCalledTimes(1); diff --git a/apps/api/src/shared/mailing/user-invitation-mailing.service.ts b/apps/api/src/shared/mailing/user-invitation-mailing.service.ts index d40e36c20..1bc22d541 100644 --- a/apps/api/src/shared/mailing/user-invitation-mailing.service.ts +++ b/apps/api/src/shared/mailing/user-invitation-mailing.service.ts @@ -17,9 +17,8 @@ import { MailerService } from '@nestjs-modules/mailer'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { ConfigServiceType } from '@/types/config-service.type'; - -import { SendMailDto } from './send-mail.dto'; +import type { ConfigServiceType } from '@/types/config-service.type'; +import type { SendMailDto } from './send-mail.dto'; @Injectable() export class UserInvitationMailingService { diff --git a/apps/api/src/utils/test-util-fixture.ts b/apps/api/src/test-utils/fixtures.ts similarity index 75% rename from apps/api/src/utils/test-util-fixture.ts rename to apps/api/src/test-utils/fixtures.ts index c039e7edd..7f07ef00a 100644 --- a/apps/api/src/utils/test-util-fixture.ts +++ b/apps/api/src/test-utils/fixtures.ts @@ -21,16 +21,16 @@ import { FieldTypeEnum, isSelectFieldFormat, } from '@/common/enums'; -import { ReplaceFieldDto } from '@/domains/channel/field/dtos'; -import { CreateFieldDto } from '@/domains/channel/field/dtos/create-field.dto'; -import { CreateIssueDto } from '@/domains/project/issue/dtos'; +import type { ReplaceFieldDto } from '@/domains/channel/field/dtos'; +import type { CreateFieldDto } from '@/domains/channel/field/dtos/create-field.dto'; +import type { CreateIssueDto } from '@/domains/project/issue/dtos'; export const createFieldEntity = (input: Partial) => { const format = input?.format ?? getRandomEnumValue(FieldFormatEnum); const type = input?.type ?? getRandomEnumValue(FieldTypeEnum); const status = input?.status ?? getRandomEnumValue(FieldStatusEnum); return { - name: faker.random.alphaNumeric(20), + name: faker.string.alphanumeric(20), description: faker.lorem.lines(2), format, type, @@ -47,8 +47,8 @@ export const createFieldDto = (input: Partial) => { const type = input?.type ?? getRandomEnumValue(FieldTypeEnum); const status = input?.status ?? getRandomEnumValue(FieldStatusEnum); return { - name: faker.random.alphaNumeric(20), - key: faker.random.alphaNumeric(20), + name: faker.string.alphanumeric(20), + key: faker.string.alphanumeric(20), description: faker.lorem.lines(2), format, type, @@ -61,14 +61,14 @@ export const createFieldDto = (input: Partial) => { }; export const updateFieldDto = (input: Partial) => { return { - id: faker.datatype.number(), + id: faker.number.int(), ...createFieldDto(input), }; }; export const createIssueDto = (input: Partial) => { return { - name: faker.random.alphaNumeric(20), + name: faker.string.alphanumeric(20), ...input, }; }; @@ -79,48 +79,44 @@ export const getRandomValue = ( ) => { switch (format) { case FieldFormatEnum.text: - return faker.datatype.string(); + return faker.string.sample(); case FieldFormatEnum.keyword: - return faker.datatype.string(); + return faker.string.sample(); case FieldFormatEnum.number: - return faker.datatype.number(); + return faker.number.int(); case FieldFormatEnum.boolean: return faker.datatype.boolean(); case FieldFormatEnum.select: return options.length === 0 ? undefined - : options[faker.datatype.number({ min: 0, max: options.length - 1 })] - .key; + : options[faker.number.int({ min: 0, max: options.length - 1 })].key; case FieldFormatEnum.multiSelect: return options.length === 0 ? [] : faker.helpers .shuffle(options) - .slice( - 0, - faker.datatype.number({ min: 0, max: options.length - 1 }), - ) + .slice(0, faker.number.int({ min: 0, max: options.length - 1 })) .map((option) => option.key); case FieldFormatEnum.date: - return faker.datatype.datetime().toISOString(); + return faker.date.anytime().toISOString(); default: throw new Error('Invalid field type '); } }; const getRandomOptionEntities = () => { - const length = faker.datatype.number(10); + const length = faker.number.int(10); return Array.from({ length }).map(() => ({ - id: faker.datatype.number(), - name: faker.datatype.string(), + id: faker.number.int(), + name: faker.string.sample(), })); }; const getRandomOptionDtos = () => { - const length = faker.datatype.number(10); + const length = faker.number.int(10); return Array.from({ length }).map(() => { - const randomValue = faker.datatype.string(); + const randomValue = faker.string.sample(); return { - id: faker.datatype.number(), + id: faker.number.int(), name: randomValue, key: randomValue, }; @@ -129,7 +125,7 @@ const getRandomOptionDtos = () => { export const getRandomEnumValue = (anEnum: T): T[keyof T] => { const enumValues = Object.keys(anEnum) as Array; - const randomIndex = faker.datatype.number(enumValues.length - 1); + const randomIndex = faker.number.int(enumValues.length - 1); const randomEnumKey = enumValues[randomIndex]; return anEnum[randomEnumKey]; }; diff --git a/apps/api/src/test-utils/providers/api-key.service.providers.ts b/apps/api/src/test-utils/providers/api-key.service.providers.ts new file mode 100644 index 000000000..7ade1d3b2 --- /dev/null +++ b/apps/api/src/test-utils/providers/api-key.service.providers.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { ApiKeyEntity } from '../../domains/project/api-key/api-key.entity'; +import { ApiKeyService } from '../../domains/project/api-key/api-key.service'; +import { ProjectServiceProviders } from './project.service.providers'; + +export const ApiKeyServiceProviders = [ + ApiKeyService, + { + provide: getRepositoryToken(ApiKeyEntity), + useValue: mockRepository(), + }, + ...ProjectServiceProviders, +]; diff --git a/apps/api/src/test-utils/providers/auth.service.providers.ts b/apps/api/src/test-utils/providers/auth.service.providers.ts new file mode 100644 index 000000000..db843ec44 --- /dev/null +++ b/apps/api/src/test-utils/providers/auth.service.providers.ts @@ -0,0 +1,52 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { JwtService } from '@nestjs/jwt'; +import { ClsService } from 'nestjs-cls'; + +import { EmailVerificationMailingService } from '@/shared/mailing/email-verification-mailing.service'; +import { CodeServiceProviders } from '@/test-utils/providers/code.service.providers'; +import { getMockProvider } from '@/test-utils/util-functions'; +import { AuthService } from '../../domains/auth/auth.service'; +import { ApiKeyServiceProviders } from './api-key.service.providers'; +import { CreateUserServiceProviders } from './create-user.service.providers'; +import { MemberServiceProviders } from './member.service.providers'; +import { RoleServiceProviders } from './role.service.providers'; +import { TenantServiceProviders } from './tenant.service.providers'; +import { UserServiceProviders } from './user.service.providers'; + +export const MockJwtService = { + sign: jest.fn(), +}; +export const MockEmailVerificationMailingService = { + send: jest.fn(), +}; + +export const AuthServiceProviders = [ + AuthService, + ...CreateUserServiceProviders, + ...UserServiceProviders, + getMockProvider(JwtService, MockJwtService), + getMockProvider( + EmailVerificationMailingService, + MockEmailVerificationMailingService, + ), + ...CodeServiceProviders, + ...ApiKeyServiceProviders, + ...TenantServiceProviders, + ...RoleServiceProviders, + ...MemberServiceProviders, + ClsService, +]; diff --git a/apps/api/src/test-utils/providers/channel.service.providers.ts b/apps/api/src/test-utils/providers/channel.service.providers.ts new file mode 100644 index 000000000..147587f7b --- /dev/null +++ b/apps/api/src/test-utils/providers/channel.service.providers.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { OpensearchRepository } from '@/common/repositories'; +import { ProjectServiceProviders } from '@/test-utils/providers/project.service.providers'; +import { + getMockProvider, + MockOpensearchRepository, + mockRepository, +} from '@/test-utils/util-functions'; +import { ChannelEntity } from '../../domains/channel/channel/channel.entity'; +import { ChannelMySQLService } from '../../domains/channel/channel/channel.mysql.service'; +import { ChannelService } from '../../domains/channel/channel/channel.service'; +import { FieldServiceProviders } from './field.service.providers'; + +export const ChannelServiceProviders = [ + ChannelService, + ChannelMySQLService, + { + provide: getRepositoryToken(ChannelEntity), + useValue: mockRepository(), + }, + getMockProvider(OpensearchRepository, MockOpensearchRepository), + ...ProjectServiceProviders, + ...FieldServiceProviders, +]; diff --git a/apps/api/src/test-utils/providers/code.service.providers.ts b/apps/api/src/test-utils/providers/code.service.providers.ts new file mode 100644 index 000000000..425c4c33e --- /dev/null +++ b/apps/api/src/test-utils/providers/code.service.providers.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { CodeEntity } from '../../shared/code/code.entity'; +import { CodeService } from '../../shared/code/code.service'; + +export const CodeServiceProviders = [ + CodeService, + { provide: getRepositoryToken(CodeEntity), useValue: mockRepository() }, +]; diff --git a/apps/api/src/test-utils/providers/create-user.service.providers.ts b/apps/api/src/test-utils/providers/create-user.service.providers.ts new file mode 100644 index 000000000..a11f23d0e --- /dev/null +++ b/apps/api/src/test-utils/providers/create-user.service.providers.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { CreateUserService } from '../../domains/user/create-user.service'; +import { UserEntity } from '../../domains/user/entities/user.entity'; +import { MemberServiceProviders } from './member.service.providers'; +import { TenantServiceProviders } from './tenant.service.providers'; +import { UserPasswordServiceProviders } from './user-password.service.providers'; + +export const CreateUserServiceProviders = [ + CreateUserService, + ...UserPasswordServiceProviders, + ...TenantServiceProviders, + ...MemberServiceProviders, + { + provide: getRepositoryToken(UserEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/api/src/test-utils/providers/feedback.service.providers.ts b/apps/api/src/test-utils/providers/feedback.service.providers.ts new file mode 100644 index 000000000..96eee876d --- /dev/null +++ b/apps/api/src/test-utils/providers/feedback.service.providers.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; +import { ClsService } from 'nestjs-cls'; + +import { OpensearchRepository } from '@/common/repositories'; +import { + getMockProvider, + MockOpensearchRepository, + mockRepository, +} from '@/test-utils/util-functions'; +import { FeedbackEntity } from '../../domains/feedback/feedback.entity'; +import { FeedbackMySQLService } from '../../domains/feedback/feedback.mysql.service'; +import { FeedbackOSService } from '../../domains/feedback/feedback.os.service'; +import { FeedbackService } from '../../domains/feedback/feedback.service'; +import { ChannelServiceProviders } from './channel.service.providers'; +import { FieldServiceProviders } from './field.service.providers'; +import { IssueServiceProviders } from './issue.service.providers'; +import { OptionServiceProviders } from './option.service.providers'; + +export const FeedbackServiceProviders = [ + FeedbackService, + FeedbackMySQLService, + { + provide: getRepositoryToken(FeedbackEntity), + useValue: mockRepository(), + }, + ClsService, + ...FieldServiceProviders, + ...IssueServiceProviders, + ...OptionServiceProviders, + ...ChannelServiceProviders, + getMockProvider(OpensearchRepository, MockOpensearchRepository), + FeedbackOSService, +]; diff --git a/apps/api/src/test-utils/providers/field.service.providers.ts b/apps/api/src/test-utils/providers/field.service.providers.ts new file mode 100644 index 000000000..7a52db316 --- /dev/null +++ b/apps/api/src/test-utils/providers/field.service.providers.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { OpensearchRepository } from '@/common/repositories'; +import { + getMockProvider, + MockOpensearchRepository, + mockRepository, +} from '@/test-utils/util-functions'; +import { FieldEntity } from '../../domains/channel/field/field.entity'; +import { FieldMySQLService } from '../../domains/channel/field/field.mysql.service'; +import { FieldService } from '../../domains/channel/field/field.service'; +import { OptionServiceProviders } from './option.service.providers'; + +export const FieldServiceProviders = [ + FieldService, + FieldMySQLService, + getMockProvider(OpensearchRepository, MockOpensearchRepository), + { + provide: getRepositoryToken(FieldEntity), + useValue: mockRepository(), + }, + ...OptionServiceProviders, +]; diff --git a/apps/api/src/test-utils/providers/issue-tracker.service.provider.ts b/apps/api/src/test-utils/providers/issue-tracker.service.provider.ts new file mode 100644 index 000000000..d7bbddc01 --- /dev/null +++ b/apps/api/src/test-utils/providers/issue-tracker.service.provider.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { IssueTrackerEntity } from '../../domains/project/issue-tracker/issue-tracker.entity'; +import { IssueTrackerService } from '../../domains/project/issue-tracker/issue-tracker.service'; + +export const IssueTrackerServiceProviders = [ + IssueTrackerService, + { + provide: getRepositoryToken(IssueTrackerEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/web/next.config.js b/apps/api/src/test-utils/providers/issue.service.providers.ts similarity index 61% rename from apps/web/next.config.js rename to apps/api/src/test-utils/providers/issue.service.providers.ts index 356a44a2d..955749d06 100644 --- a/apps/web/next.config.js +++ b/apps/api/src/test-utils/providers/issue.service.providers.ts @@ -13,22 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -const { i18n } = require('./next-i18next.config'); -const path = require('path'); +import { getRepositoryToken } from '@nestjs/typeorm'; -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: false, - swcMinify: true, - i18n, - output: 'standalone', - experimental: { - outputFileTracingRoot: path.join(__dirname, '../../'), - }, - eslint: { - ignoreDuringBuilds: true, - }, - transpilePackages: ['@ufb/ui'], -}; +import { mockRepository } from '@/test-utils/util-functions'; +import { IssueEntity } from '../../domains/project/issue/issue.entity'; +import { IssueService } from '../../domains/project/issue/issue.service'; -module.exports = nextConfig; +export const IssueServiceProviders = [ + IssueService, + { + provide: getRepositoryToken(IssueEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/api/src/test-utils/providers/member.service.providers.ts b/apps/api/src/test-utils/providers/member.service.providers.ts new file mode 100644 index 000000000..cac4025ba --- /dev/null +++ b/apps/api/src/test-utils/providers/member.service.providers.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { MemberEntity } from '../../domains/project/member/member.entity'; +import { MemberService } from '../../domains/project/member/member.service'; +import { RoleServiceProviders } from './role.service.providers'; + +export const MemberServiceProviders = [ + MemberService, + { + provide: getRepositoryToken(MemberEntity), + useValue: mockRepository(), + }, + ...RoleServiceProviders, +]; diff --git a/apps/api/src/test-utils/providers/option.service.providers.ts b/apps/api/src/test-utils/providers/option.service.providers.ts new file mode 100644 index 000000000..2fd57cc67 --- /dev/null +++ b/apps/api/src/test-utils/providers/option.service.providers.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { OptionEntity } from '../../domains/channel/option/option.entity'; +import { OptionService } from '../../domains/channel/option/option.service'; + +export const OptionServiceProviders = [ + OptionService, + { + provide: getRepositoryToken(OptionEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/api/src/test-utils/providers/project.service.providers.ts b/apps/api/src/test-utils/providers/project.service.providers.ts new file mode 100644 index 000000000..2145205ce --- /dev/null +++ b/apps/api/src/test-utils/providers/project.service.providers.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { OpensearchRepository } from '@/common/repositories'; +import { ChannelEntity } from '@/domains/channel/channel/channel.entity'; +import { TenantServiceProviders } from '@/test-utils/providers/tenant.service.providers'; +import { + getMockProvider, + MockOpensearchRepository, + mockRepository, +} from '@/test-utils/util-functions'; +import { ProjectEntity } from '../../domains/project/project/project.entity'; +import { ProjectService } from '../../domains/project/project/project.service'; +import { RoleServiceProviders } from './role.service.providers'; + +export const ProjectServiceProviders = [ + ProjectService, + { + provide: getRepositoryToken(ProjectEntity), + useValue: mockRepository(), + }, + { + provide: getRepositoryToken(ChannelEntity), + useValue: mockRepository(), + }, + getMockProvider(OpensearchRepository, MockOpensearchRepository), + ...TenantServiceProviders, + ...RoleServiceProviders, +]; diff --git a/apps/api/src/test-utils/providers/role.service.providers.ts b/apps/api/src/test-utils/providers/role.service.providers.ts new file mode 100644 index 000000000..7f8b2074f --- /dev/null +++ b/apps/api/src/test-utils/providers/role.service.providers.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { RoleEntity } from '../../domains/project/role/role.entity'; +import { RoleService } from '../../domains/project/role/role.service'; + +export const RoleServiceProviders = [ + RoleService, + { + provide: getRepositoryToken(RoleEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/api/src/test-utils/providers/tenant.service.providers.ts b/apps/api/src/test-utils/providers/tenant.service.providers.ts new file mode 100644 index 000000000..419f33dca --- /dev/null +++ b/apps/api/src/test-utils/providers/tenant.service.providers.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { mockRepository } from '@/test-utils/util-functions'; +import { FeedbackEntity } from '../../domains/feedback/feedback.entity'; +import { TenantEntity } from '../../domains/tenant/tenant.entity'; +import { TenantService } from '../../domains/tenant/tenant.service'; +import { UserEntity } from '../../domains/user/entities/user.entity'; + +export const TenantServiceProviders = [ + TenantService, + { + provide: getRepositoryToken(TenantEntity), + useValue: mockRepository(), + }, + { + provide: getRepositoryToken(UserEntity), + useValue: mockRepository(), + }, + { + provide: getRepositoryToken(FeedbackEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/api/src/test-utils/providers/user-password.service.providers.ts b/apps/api/src/test-utils/providers/user-password.service.providers.ts new file mode 100644 index 000000000..b4aac26c7 --- /dev/null +++ b/apps/api/src/test-utils/providers/user-password.service.providers.ts @@ -0,0 +1,41 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { MailerService } from '@nestjs-modules/mailer'; +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { ResetPasswordMailingService } from '@/shared/mailing/reset-password-mailing.service'; +import { CodeServiceProviders } from '@/test-utils/providers/code.service.providers'; +import { getMockProvider, mockRepository } from '@/test-utils/util-functions'; +import { UserEntity } from '../../domains/user/entities/user.entity'; +import { UserPasswordService } from '../../domains/user/user-password.service'; + +const MockMailerService = { + sendMail: jest.fn(), +}; +const MockResetPasswordMailingService = { + send: jest.fn(), +}; + +export const UserPasswordServiceProviders = [ + UserPasswordService, + ...CodeServiceProviders, + getMockProvider(ResetPasswordMailingService, MockResetPasswordMailingService), + getMockProvider(MailerService, MockMailerService), + { + provide: getRepositoryToken(UserEntity), + useValue: mockRepository(), + }, +]; diff --git a/apps/api/src/test-utils/providers/user.service.providers.ts b/apps/api/src/test-utils/providers/user.service.providers.ts new file mode 100644 index 000000000..2874893fb --- /dev/null +++ b/apps/api/src/test-utils/providers/user.service.providers.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { MailerService } from '@nestjs-modules/mailer'; +import { getRepositoryToken } from '@nestjs/typeorm'; + +import { UserInvitationMailingService } from '@/shared/mailing/user-invitation-mailing.service'; +import { CodeServiceProviders } from '@/test-utils/providers/code.service.providers'; +import { getMockProvider, mockRepository } from '@/test-utils/util-functions'; +import { UserEntity } from '../../domains/user/entities/user.entity'; +import { UserService } from '../../domains/user/user.service'; +import { TenantServiceProviders } from './tenant.service.providers'; + +export const MockUserInvitationMailingService = { + send: jest.fn(), +}; +const MockMailerService = { + sendMail: jest.fn(), +}; + +export const UserServiceProviders = [ + UserService, + { provide: getRepositoryToken(UserEntity), useValue: mockRepository() }, + getMockProvider( + UserInvitationMailingService, + MockUserInvitationMailingService, + ), + getMockProvider(MailerService, MockMailerService), + ...CodeServiceProviders, + ...TenantServiceProviders, +]; diff --git a/apps/api/src/utils/test-utils.ts b/apps/api/src/test-utils/util-functions.ts similarity index 93% rename from apps/api/src/utils/test-utils.ts rename to apps/api/src/test-utils/util-functions.ts index 9a36a2a20..a772f74e1 100644 --- a/apps/api/src/utils/test-utils.ts +++ b/apps/api/src/test-utils/util-functions.ts @@ -14,13 +14,13 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { InjectionToken, Provider } from '@nestjs/common'; +import type { InjectionToken, Provider } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { initializeTransactionalContext } from 'typeorm-transactional'; import { smtpConfig, smtpConfigSchema } from '@/configs/smtp.config'; -import { AuthService } from '@/domains/auth/auth.service'; +import type { AuthService } from '@/domains/auth/auth.service'; import { UserDto } from '@/domains/user/dtos'; import { UserStateEnum } from '@/domains/user/entities/enums'; import { UserEntity } from '@/domains/user/entities/user.entity'; @@ -46,7 +46,7 @@ export const MockDataSource = { export const getRandomEnumValue = (anEnum: T): T[keyof T] => { const enumValues = Object.keys(anEnum) as Array; - const randomIndex = faker.datatype.number(enumValues.length - 1); + const randomIndex = faker.number.int(enumValues.length - 1); const randomEnumKey = enumValues[randomIndex]; return anEnum[randomEnumKey]; }; diff --git a/apps/api/src/types/cls-service.type.ts b/apps/api/src/types/cls-service.type.ts index 7eddc0e80..45a6f1bee 100644 --- a/apps/api/src/types/cls-service.type.ts +++ b/apps/api/src/types/cls-service.type.ts @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { ClsStore } from 'nestjs-cls'; +import type { ClsStore } from 'nestjs-cls'; export interface ClsServiceType extends ClsStore { userId?: number; diff --git a/apps/api/src/types/config-service.type.ts b/apps/api/src/types/config-service.type.ts index d06874f87..0b7f34703 100644 --- a/apps/api/src/types/config-service.type.ts +++ b/apps/api/src/types/config-service.type.ts @@ -13,13 +13,13 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { ConfigType } from '@nestjs/config'; +import type { ConfigType } from '@nestjs/config'; -import { appConfig } from '@/configs/app.config'; -import { jwtConfig } from '@/configs/jwt.config'; -import { mysqlConfig } from '@/configs/mysql.config'; -import { opensearchConfig } from '@/configs/opensearch.config'; -import { smtpConfig } from '@/configs/smtp.config'; +import type { appConfig } from '@/configs/app.config'; +import type { jwtConfig } from '@/configs/jwt.config'; +import type { mysqlConfig } from '@/configs/mysql.config'; +import type { opensearchConfig } from '@/configs/opensearch.config'; +import type { smtpConfig } from '@/configs/smtp.config'; export type ConfigServiceType = { app: ConfigType; diff --git a/apps/api/test/auth.e2e-spec.ts b/apps/api/test/auth.e2e-spec.ts index 9194cf7f5..3a8a6c760 100644 --- a/apps/api/test/auth.e2e-spec.ts +++ b/apps/api/test/auth.e2e-spec.ts @@ -14,12 +14,14 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import request from 'supertest'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { @@ -29,7 +31,7 @@ import { EmailVerificationMailingRequestDto, InvitationUserSignUpRequestDto, } from '@/domains/auth/dtos/requests'; -import { RoleEntity } from '@/domains/project/role/role.entity'; +import type { RoleEntity } from '@/domains/project/role/role.entity'; import { TenantEntity } from '@/domains/tenant/tenant.entity'; import { UserStateEnum, UserTypeEnum } from '@/domains/user/entities/enums'; import { UserEntity } from '@/domains/user/entities/user.entity'; @@ -37,7 +39,7 @@ import { UserPasswordService } from '@/domains/user/user-password.service'; import { CodeTypeEnum } from '@/shared/code/code-type.enum'; import { CodeEntity } from '@/shared/code/code.entity'; import { CodeService } from '@/shared/code/code.service'; -import { clearEntities } from '@/utils/test-utils'; +import { clearEntities } from '@/test-utils/util-functions'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -83,7 +85,7 @@ describe('AppController (e2e)', () => { allowDomains: [], isPrivate: false, isRestrictDomain: false, - siteName: faker.datatype.string(), + siteName: faker.string.sample(), }); }); @@ -146,7 +148,7 @@ describe('AppController (e2e)', () => { it('invalid code', async () => { const dto = new EmailVerificationCodeRequestDto(); dto.email = email; - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); const originalCode = await codeRepo.findOneBy({ code }); expect(originalCode.isVerified).toEqual(false); @@ -277,7 +279,7 @@ describe('AppController (e2e)', () => { }); it('no invitation', async () => { const dto = new InvitationUserSignUpRequestDto(); - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); dto.email = email; dto.password = faker.internet.password(); @@ -290,7 +292,7 @@ describe('AppController (e2e)', () => { await setCode(email); const dto = new InvitationUserSignUpRequestDto(); - dto.code = faker.datatype.string(); + dto.code = faker.string.sample(); dto.email = email; dto.password = faker.internet.password(); diff --git a/apps/api/test/feedback/channel.e2e-spec.ts b/apps/api/test/feedback/channel.e2e-spec.ts index 1607d02f0..997e5d497 100644 --- a/apps/api/test/feedback/channel.e2e-spec.ts +++ b/apps/api/test/feedback/channel.e2e-spec.ts @@ -14,12 +14,14 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import { Client } from '@opensearch-project/opensearch/.'; import request from 'supertest'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { @@ -38,8 +40,11 @@ import { FieldEntity } from '@/domains/channel/field/field.entity'; import { FIELD_TYPES_TO_MAPPING_TYPES } from '@/domains/channel/field/field.mysql.service'; import { OptionEntity } from '@/domains/channel/option/option.entity'; import { ProjectEntity } from '@/domains/project/project/project.entity'; -import { createFieldDto, optionSort } from '@/utils/test-util-fixture'; -import { DEFAULT_FIELD_COUNT, clearEntities } from '@/utils/test-utils'; +import { createFieldDto, optionSort } from '@/test-utils/fixtures'; +import { + clearEntities, + DEFAULT_FIELD_COUNT, +} from '@/test-utils/util-functions'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -88,17 +93,17 @@ describe('AppController (e2e)', () => { await clearEntities([projectRepo, channelRepo, fieldRepo, optionRepo]); project = await projectRepo.save({ - name: faker.datatype.string(), - description: faker.datatype.string(), + name: faker.string.sample(), + description: faker.string.sample(), }); }); it('/projects/:projectId/channels (POST)', () => { - const fieldCount = faker.datatype.number({ min: 1, max: 10 }); + const fieldCount = faker.number.int({ min: 1, max: 10 }); const dto = new CreateChannelRequestDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); dto.fields = Array.from({ length: fieldCount }).map((_) => createFieldDto({}), ); @@ -158,7 +163,7 @@ describe('AppController (e2e)', () => { }); it('/projects/:projectId/channels (GET)', async () => { - const total = faker.datatype.number(10); + const total = faker.number.int(10); await channelRepo.save( Array.from({ length: total }).map(() => ({ @@ -187,8 +192,8 @@ describe('AppController (e2e)', () => { it('/channels/:id (GET)', async () => { const channel = await channelRepo.save({ - name: faker.datatype.string(), - description: faker.datatype.string(), + name: faker.string.sample(), + description: faker.string.sample(), }); return request(app.getHttpServer()) @@ -201,12 +206,12 @@ describe('AppController (e2e)', () => { }); }); it('/channels/:id (PUT)', async () => { - const fieldCount = faker.datatype.number({ min: 1, max: 10 }); + const fieldCount = faker.number.int({ min: 1, max: 10 }); const { id: channelId } = await channelService.create({ projectId: project.id, - name: faker.datatype.string(), - description: faker.datatype.string(), + name: faker.string.sample(), + description: faker.string.sample(), fields: Array.from({ length: fieldCount }).map((_) => createFieldDto({})), }); @@ -224,8 +229,8 @@ describe('AppController (e2e)', () => { ); const dto = new UpdateChannelRequestDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); // dto.fields = [...existingFields, ...newfields]; return request(app.getHttpServer()) diff --git a/apps/api/test/feedback/feedback.e2e-spec.ts b/apps/api/test/feedback/feedback.e2e-spec.ts index 7f011b1b2..a4421f064 100644 --- a/apps/api/test/feedback/feedback.e2e-spec.ts +++ b/apps/api/test/feedback/feedback.e2e-spec.ts @@ -14,12 +14,14 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Client } from '@opensearch-project/opensearch/.'; import request from 'supertest'; -import { Repository } from 'typeorm'; +import type { Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { FieldFormatEnum } from '@/common/enums'; @@ -30,8 +32,8 @@ import { FieldEntity } from '@/domains/channel/field/field.entity'; import { FeedbackService } from '@/domains/feedback/feedback.service'; import { ProjectEntity } from '@/domains/project/project/project.entity'; import { ProjectService } from '@/domains/project/project/project.service'; -import { createFieldDto, getRandomValue } from '@/utils/test-util-fixture'; -import { clearEntities } from '@/utils/test-utils'; +import { createFieldDto, getRandomValue } from '@/test-utils/fixtures'; +import { clearEntities } from '@/test-utils/util-functions'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -84,10 +86,10 @@ describe('AppController (e2e)', () => { const { id: channelId } = await channelService.create({ projectId, - name: faker.random.alphaNumeric(20), + name: faker.string.alphanumeric(20), description: faker.lorem.lines(1), fields: Array.from({ - length: faker.datatype.number({ min: 1, max: 10 }), + length: faker.number.int({ min: 1, max: 10 }), }).map(createFieldDto), }); @@ -126,7 +128,7 @@ describe('AppController (e2e)', () => { }); it('/channels/:channelId/feedbacks (GET)', async () => { - const feedbackCount = faker.datatype.number({ min: 1, max: 10 }); + const feedbackCount = faker.number.int({ min: 1, max: 10 }); const dataset = []; for (let i = 0; i < feedbackCount; i++) { const data = {}; @@ -163,8 +165,7 @@ describe('AppController (e2e)', () => { data, }); - const targetField = - targetFields[faker.datatype.number(targetFields.length - 1)]; + const targetField = targetFields[faker.number.int(targetFields.length - 1)]; const newValue = getRandomValue(targetField.format, targetField.options); @@ -189,8 +190,8 @@ describe('AppController (e2e)', () => { }); // const channel = await channelModel.create({ - // name: faker.datatype.string(), - // description: faker.datatype.string(), + // name: faker.string.sample(), + // description: faker.string.sample(), // }); // return request(app.getHttpServer()) @@ -204,19 +205,19 @@ describe('AppController (e2e)', () => { // }); // it('/channels/:id (PUT)', async () => { // const channel = await channelModel.create({ - // name: faker.datatype.string(), - // description: faker.datatype.string(), + // name: faker.string.sample(), + // description: faker.string.sample(), // }); // await fieldModel.create( - // Array.from({ length: faker.datatype.number({ min: 1, max: 5 }) }) + // Array.from({ length: faker.number.int({ min: 1, max: 5 }) }) // .map(createField) // .map((v) => ({ ...v, channel: { _id: channel.id } })), // ); // const dto = new UpdateChannelRequestDto(); - // dto.name = faker.datatype.string(); - // dto.description = faker.datatype.string(); + // dto.name = faker.string.sample(); + // dto.description = faker.string.sample(); // dto.fields = []; // return request(app.getHttpServer()) @@ -234,8 +235,8 @@ describe('AppController (e2e)', () => { // }); // it('/channels/:id (DELETE)', async () => { // const channel = await channelModel.create({ - // name: faker.datatype.string(), - // description: faker.datatype.string(), + // name: faker.string.sample(), + // description: faker.string.sample(), // }); // await request(app.getHttpServer()) diff --git a/apps/api/test/feedback/project.e2e-spec.ts b/apps/api/test/feedback/project.e2e-spec.ts index d59cdaa83..abfc6ca84 100644 --- a/apps/api/test/feedback/project.e2e-spec.ts +++ b/apps/api/test/feedback/project.e2e-spec.ts @@ -14,17 +14,19 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import request from 'supertest'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { HttpExceptionFilter } from '@/common/filters'; import { CreateProjectRequestDto } from '@/domains/project/project/dtos/requests'; import { ProjectEntity } from '@/domains/project/project/project.entity'; -import { clearEntities } from '@/utils/test-utils'; +import { clearEntities } from '@/test-utils/util-functions'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -61,12 +63,12 @@ describe('AppController (e2e)', () => { }); it('/projects (GET)', async () => { - const total = faker.datatype.number(10); + const total = faker.number.int(10); await projectRepo.save( Array.from({ length: total }).map(() => ({ - name: faker.datatype.string(), - description: faker.datatype.string(), + name: faker.string.sample(), + description: faker.string.sample(), })), ); @@ -89,16 +91,16 @@ describe('AppController (e2e)', () => { it('/projects (POST)', () => { const dto = new CreateProjectRequestDto(); - dto.name = faker.datatype.string(); - dto.description = faker.datatype.string(); + dto.name = faker.string.sample(); + dto.description = faker.string.sample(); return request(app.getHttpServer()).post('/projects').send(dto).expect(201); }); it('/projects/:id (GET)', async () => { const project = await projectRepo.save({ - name: faker.datatype.string(), - description: faker.datatype.string(), + name: faker.string.sample(), + description: faker.string.sample(), }); return request(app.getHttpServer()) @@ -112,12 +114,12 @@ describe('AppController (e2e)', () => { }); it('/projects/:id (PUT)', async () => { const project = await projectRepo.save({ - name: faker.datatype.string(), - description: faker.datatype.string(), + name: faker.string.sample(), + description: faker.string.sample(), }); - const name = faker.datatype.string(); - const description = faker.datatype.string(); + const name = faker.string.sample(); + const description = faker.string.sample(); return request(app.getHttpServer()) .put(`/projects/${project.id}`) @@ -131,8 +133,8 @@ describe('AppController (e2e)', () => { }); // it('/projects/:id (DELETE)', async () => { // const project = await projectModel.create({ - // name: faker.datatype.string(), - // description: faker.datatype.string(), + // name: faker.string.sample(), + // description: faker.string.sample(), // }); // await request(app.getHttpServer()) diff --git a/apps/api/test/role.e2e-spec.ts b/apps/api/test/role.e2e-spec.ts index 755887bb2..1ae2ba75d 100644 --- a/apps/api/test/role.e2e-spec.ts +++ b/apps/api/test/role.e2e-spec.ts @@ -14,23 +14,25 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import request from 'supertest'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { AuthService } from '@/domains/auth/auth.service'; import { UpdateRoleRequestDto } from '@/domains/project/role/dtos/requests'; import { PermissionEnum } from '@/domains/project/role/permission.enum'; import { RoleEntity } from '@/domains/project/role/role.entity'; -import { HttpStatusCode } from '@/types/http-status'; import { clearEntities, getRandomEnumValues, signInTestUser, -} from '@/utils/test-utils'; +} from '@/test-utils/util-functions'; +import { HttpStatusCode } from '@/types/http-status'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -70,10 +72,10 @@ describe('AppController (e2e)', () => { describe('/roles (GET)', () => { it('positive case', async () => { - const total = faker.datatype.number(20); + const total = faker.number.int(20); for (let i = 0; i < total; i++) { await roleRepo.save({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: getRandomEnumValues(PermissionEnum), }); } @@ -108,7 +110,7 @@ describe('AppController (e2e)', () => { .post('/roles') .set('Authorization', `Bearer ${accessToken}`) .send({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: getRandomEnumValues(PermissionEnum), }) .expect(201); @@ -117,7 +119,7 @@ describe('AppController (e2e)', () => { return request(app.getHttpServer()) .post('/roles') .send({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: getRandomEnumValues(PermissionEnum), }) .expect(HttpStatusCode.UNAUTHORIZED); @@ -126,7 +128,7 @@ describe('AppController (e2e)', () => { it('/roles/:id (GET)', async () => { const role = await roleRepo.save({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: getRandomEnumValues(PermissionEnum), }); @@ -142,7 +144,7 @@ describe('AppController (e2e)', () => { describe('/roles/:id (PUT)', () => { it('positive case', async () => { const role = await roleRepo.save({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: getRandomEnumValues(PermissionEnum), }); @@ -163,14 +165,14 @@ describe('AppController (e2e)', () => { }); it('Unauthrized', async () => { await request(app.getHttpServer()) - .put(`/roles/${faker.datatype.number()}`) + .put(`/roles/${faker.number.int()}`) .expect(HttpStatusCode.UNAUTHORIZED); }); }); describe('/roles/:id (DELETE)', () => { it('positive case', async () => { const role = await roleRepo.save({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: getRandomEnumValues(PermissionEnum), }); @@ -184,7 +186,7 @@ describe('AppController (e2e)', () => { }); it('Unauthrized', async () => { await request(app.getHttpServer()) - .delete(`/roles/${faker.datatype.number()}`) + .delete(`/roles/${faker.number.int()}`) .expect(HttpStatusCode.UNAUTHORIZED); }); }); diff --git a/apps/api/test/tenant.e2e-spec.ts b/apps/api/test/tenant.e2e-spec.ts index 0cf38491a..035b3e93b 100644 --- a/apps/api/test/tenant.e2e-spec.ts +++ b/apps/api/test/tenant.e2e-spec.ts @@ -14,11 +14,13 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import request from 'supertest'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { AuthService } from '@/domains/auth/auth.service'; @@ -27,8 +29,8 @@ import { UpdateTenantRequestDto, } from '@/domains/tenant/dtos/requests'; import { TenantEntity } from '@/domains/tenant/tenant.entity'; +import { clearEntities, signInTestUser } from '@/test-utils/util-functions'; import { HttpStatusCode } from '@/types/http-status'; -import { clearEntities, signInTestUser } from '@/utils/test-utils'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -65,7 +67,7 @@ describe('AppController (e2e)', () => { describe('/tenant (POST)', () => { it('setup', async () => { const dto = new SetupTenantRequestDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); return request(app.getHttpServer()) .post('/tenant') @@ -83,13 +85,13 @@ describe('AppController (e2e)', () => { }); it('already exists', async () => { await tenantRepo.save({ - siteName: faker.datatype.string(), + siteName: faker.string.sample(), isPrivate: faker.datatype.boolean(), isRestrictDomain: faker.datatype.boolean(), allowDomains: [], }); const dto = new SetupTenantRequestDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); return request(app.getHttpServer()).post('/tenant').send(dto).expect(400); }); @@ -99,7 +101,7 @@ describe('AppController (e2e)', () => { let accessToken: string; beforeEach(async () => { tenant = await tenantRepo.save({ - siteName: faker.datatype.string(), + siteName: faker.string.sample(), isPrivate: faker.datatype.boolean(), isRestrictDomain: faker.datatype.boolean(), allowDomains: [], @@ -110,7 +112,7 @@ describe('AppController (e2e)', () => { it('update', async () => { const dto = new UpdateTenantRequestDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); dto.isPrivate = faker.datatype.boolean(); dto.isRestrictDomain = faker.datatype.boolean(); dto.allowDomains = []; @@ -135,7 +137,7 @@ describe('AppController (e2e)', () => { const dto = new UpdateTenantRequestDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); dto.isPrivate = faker.datatype.boolean(); dto.isRestrictDomain = faker.datatype.boolean(); dto.allowDomains = []; @@ -150,7 +152,7 @@ describe('AppController (e2e)', () => { it('not found role', async () => { const dto = new UpdateTenantRequestDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); dto.isPrivate = faker.datatype.boolean(); dto.isRestrictDomain = faker.datatype.boolean(); dto.allowDomains = []; @@ -164,7 +166,7 @@ describe('AppController (e2e)', () => { it('unauthorized', async () => { const dto = new UpdateTenantRequestDto(); - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); dto.isPrivate = faker.datatype.boolean(); dto.isRestrictDomain = faker.datatype.boolean(); dto.allowDomains = []; @@ -178,7 +180,7 @@ describe('AppController (e2e)', () => { describe('/tenant (GET)', () => { const dto = new SetupTenantRequestDto(); beforeEach(async () => { - dto.siteName = faker.datatype.string(); + dto.siteName = faker.string.sample(); await request(app.getHttpServer()).post('/tenant').send(dto); }); diff --git a/apps/api/test/user.e2e-spec.ts b/apps/api/test/user.e2e-spec.ts index 411700647..683f38c5a 100644 --- a/apps/api/test/user.e2e-spec.ts +++ b/apps/api/test/user.e2e-spec.ts @@ -14,24 +14,26 @@ * under the License. */ import { faker } from '@faker-js/faker'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; +import type { INestApplication } from '@nestjs/common'; +import { ValidationPipe } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import { Test } from '@nestjs/testing'; import { getDataSourceToken } from '@nestjs/typeorm'; import dayjs from 'dayjs'; import request from 'supertest'; -import { DataSource, Repository } from 'typeorm'; +import type { DataSource, Repository } from 'typeorm'; import { AppModule } from '@/app.module'; import { AuthService } from '@/domains/auth/auth.service'; import { RoleEntity } from '@/domains/project/role/role.entity'; import { UserStateEnum } from '@/domains/user/entities/enums'; import { UserEntity } from '@/domains/user/entities/user.entity'; -import { HttpStatusCode } from '@/types/http-status'; import { clearEntities, getRandomEnumValue, signInTestUser, -} from '@/utils/test-utils'; +} from '@/test-utils/util-functions'; +import { HttpStatusCode } from '@/types/http-status'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -71,7 +73,7 @@ describe('AppController (e2e)', () => { beforeEach(async () => { await clearEntities([userRepo, roleRepo]); - const length = faker.datatype.number({ min: 20, max: 30 }); + const length = faker.number.int({ min: 20, max: 30 }); userEntities = ( await userRepo.save( @@ -123,8 +125,8 @@ describe('AppController (e2e)', () => { }); it('page and limit', async () => { - const limit = faker.datatype.number({ min: 1, max: 10 }); - const page = faker.datatype.number({ + const limit = faker.number.int({ min: 1, max: 10 }); + const page = faker.number.int({ min: 1, max: Math.floor(total / limit), }); @@ -208,7 +210,7 @@ describe('AppController (e2e)', () => { }); it('Unauthorization', async () => { return request(app.getHttpServer()) - .delete(`/users/${faker.datatype.number()}`) + .delete(`/users/${faker.number.int()}`) .set('Authorization', `Bearer ${accessToken}`) .expect(HttpStatusCode.UNAUTHORIZED); }); @@ -222,7 +224,7 @@ describe('AppController (e2e)', () => { describe('/users/:id/role (PUT)', () => { it('positive case', async () => { const role = await roleRepo.save({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: [], }); @@ -234,7 +236,7 @@ describe('AppController (e2e)', () => { }); it('Unauthroized', async () => { const role = await roleRepo.save({ - name: faker.datatype.string(), + name: faker.string.sample(), permissions: [], }); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index 1c1aff619..ff80861b1 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -4,18 +4,9 @@ "outDir": "./dist", "baseUrl": "./", "paths": { - "@/*": [ - "./src/*" - ] - }, - "sourceMap": true + "@/*": ["./src/*"] + } }, - "include": [ - "src", - "test" - ], - "exclude": [ - "node_modules", - "dist", - ] -} \ No newline at end of file + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js deleted file mode 100644 index b29d25037..000000000 --- a/apps/web/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - root: true, - extends: ['next', 'ufb'], - ignorePatterns: ['.next', '.turbo', 'openapi.type.ts', 'api.type.ts'], - rules: { - '@next/next/no-html-link-for-pages': 'off', - 'react-hooks/exhaustive-deps': 'off', - '@typescript-eslint/no-empty-function': 'off', - }, -}; diff --git a/apps/web/README.md b/apps/web/README.md index c435881e5..16c73cbb6 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -24,6 +24,10 @@ You will also see any lint errors in the console. yarn dev ``` +> **Note** +> In order to run web properly, ui packages need to be built by the +> `yarn build:ui` command in root directory or `yarn turbo run @ufb/ui#build` command in any directory. + ### `generate-api-type` Generate api type using open api specification. This command can run after running on server. The type file is generated in `src/types/api.type.ts` diff --git a/apps/web/jest.config.js b/apps/web/jest.config.mjs similarity index 79% rename from apps/web/jest.config.js rename to apps/web/jest.config.mjs index c29878ae9..067032bbb 100644 --- a/apps/web/jest.config.js +++ b/apps/web/jest.config.mjs @@ -1,4 +1,4 @@ -const nextJest = require('next/jest'); +import nextJest from 'next/jest.js'; const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment @@ -8,8 +8,6 @@ const createJestConfig = nextJest({ // Add any custom config to be passed to Jest /** @type {import('jest').Config} */ const jestConfig = { - // Add more setup options before each test is run - setupFilesAfterEnv: ['/jest.setup.js'], // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work moduleDirectories: ['node_modules', '/'], testEnvironment: 'jest-environment-jsdom', @@ -23,4 +21,4 @@ const jestConfig = { }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async -module.exports = createJestConfig(jestConfig); +export default createJestConfig(jestConfig); diff --git a/apps/web/jest.setup.js b/apps/web/jest.setup.js deleted file mode 100644 index 060ffd261..000000000 --- a/apps/web/jest.setup.js +++ /dev/null @@ -1,20 +0,0 @@ -// Optional: configure or set up a testing framework before each test. -// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` - -// Used for __tests__/testing-library.js -// Learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect'; - -jest.mock('next/router', () => require('next-router-mock')); -jest.mock('next/dist/client/router', () => require('next-router-mock')); - -jest.mock('next-i18next', () => ({ - // this mock makes sure any components using the translate hook can use it without a warning being shown - useTranslation: () => ({ t: (str) => str }), -})); -delete window.ResizeObserver; -window.ResizeObserver = jest.fn().mockImplementation(() => ({ - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn(), -})); diff --git a/apps/web/next-i18next.config.js b/apps/web/next-i18next.config.js index a37c1ff4f..9dcfda2fd 100644 --- a/apps/web/next-i18next.config.js +++ b/apps/web/next-i18next.config.js @@ -1,18 +1,3 @@ -/** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ module.exports = { i18n: { defaultLocale: 'default', diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 000000000..1ea4eb881 --- /dev/null +++ b/apps/web/next.config.mjs @@ -0,0 +1,47 @@ +import './src/env.mjs'; + +import path from 'path'; +import { fileURLToPath } from 'url'; + +import i18nConfig from './next-i18next.config.js'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: false, + swcMinify: true, + i18n: i18nConfig.i18n, + output: 'standalone', + experimental: { outputFileTracingRoot: path.join(__dirname, '../../') }, + eslint: { ignoreDuringBuilds: true }, + transpilePackages: ['@ufb/ui'], + compiler: { removeConsole: process.env.NODE_ENV === 'production' }, + webpack(config) { + const fileLoaderRule = config.module.rules.find((rule) => + rule.test?.test?.('.svg'), + ); + + config.module.rules.push( + { ...fileLoaderRule, test: /\.svg$/i, resourceQuery: /url/ }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + resourceQuery: { not: /url/ }, + use: [ + { + loader: '@svgr/webpack', + options: { icon: true, exportType: 'named' }, + }, + ], + }, + ); + + // Modify the file loader rule to ignore *.svg, since we have it handled now. + fileLoaderRule.exclude = /\.svg$/i; + + return config; + }, +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json index f2d0cb241..e95ab8802 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -3,86 +3,93 @@ "version": "0.0.0", "private": true, "scripts": { - "dev": "next dev", "build": "next build", + "clean": "git clean -xdf .next .turbo node_modules", + "dev": "next dev", + "format": "prettier --check \"./src/**/*.{js,cjs,mjs,ts,tsx,md,json}\"", + "generate-api-type": "openapi-typescript http://0.0.0.0:4000/docs-json --output src/types/api.type.ts", + "lint": "SKIP_ENV_VALIDATION=1 next lint", "start": "next start", - "lint": "next lint", "test": "jest --passWithNoTests", - "test:dev": "jest --watch --passWithNoTests", "test:ci": "jest --ci --passWithNoTests", - "generate-api-type": "openapi-typescript http://0.0.0.0:4000/docs-json --output src/types/api.type.ts" + "test:dev": "jest --watch --passWithNoTests", + "typecheck": "tsc --noEmit" + }, + "prettier": "@ufb/prettier-config", + "eslintConfig": { + "extends": [ + "@ufb/eslint-config/base", + "@ufb/eslint-config/react", + "@ufb/eslint-config/nextjs" + ], + "ignorePatterns": [ + "api.type.ts" + ], + "root": true }, "dependencies": { - "@emotion/react": "^11.11.0", - "@emotion/styled": "^11.11.0", - "@faker-js/faker": "^8.0.2", - "@floating-ui/react": "^0.24.6", - "@headlessui/react": "1.7.13", - "@headlessui/tailwindcss": "^0.1.3", - "@hookform/resolvers": "^2.9.11", - "@mui/base": "^5.0.0-beta.4", - "@tanstack/react-query": "^4.29.12", - "@tanstack/react-table": "^8.10.0", - "@ufb/tailwind": "*", - "@ufb/ui": "*", - "axios": "^1.4.0", + "@faker-js/faker": "^8.2.0", + "@headlessui/react": "1.7.17", + "@headlessui/tailwindcss": "^0.2.0", + "@hookform/resolvers": "^3.3.2", + "@t3-oss/env-nextjs": "^0.7.1", + "@tanstack/react-query": "^5.0.0", + "@tanstack/react-table": "^8.10.7", + "@ufb/tailwind": "^0.1.0", + "@ufb/ui": "^0.1.0", + "axios": "^1.5.1", "axios-auth-refresh": "^3.3.6", - "cookies-next": "^2.1.2", + "cookies-next": "^4.0.0", "date-fns": "^2.30.0", - "dayjs": "^1.11.9", - "framer-motion": "^8.5.5", - "i18next": "^22.5.1", - "immer": "^9.0.21", + "dayjs": "^1.11.10", + "framer-motion": "^10.16.4", + "i18next": "^23.6.0", + "immer": "^10.0.3", "iron-session": "^6.3.1", "jwt-decode": "^3.1.2", - "next": "^13.4.4", - "next-i18next": "^13.3.0", - "pino": "^8.14.1", + "next": "^13.5.6", + "next-i18next": "^14.0.3", + "pino": "^8.16.0", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", - "react-datepicker": "^4.12.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", + "react-datepicker": "^4.21.0", "react-dom": "^18.2.0", - "react-hook-form": "^7.44.3", + "react-hook-form": "^7.47.0", "react-hot-toast": "^2.4.1", - "react-i18next": "^12.3.1", - "react-select": "^5.7.3", - "react-tailwindcss-datepicker": "^1.6.1", + "react-i18next": "^13.3.1", + "react-select": "^5.7.7", "react-use": "^17.4.0", - "sharp": "^0.32.1", + "sharp": "^0.32.6", "tailwind-scrollbar-hide": "^1.1.7", - "zod": "^3.21.4", - "zustand": "^4.3.8" + "zod": "^3.22.4", + "zustand": "^4.4.3" }, "devDependencies": { - "@babel/core": "^7.22.1", - "@rollup/plugin-commonjs": "^24.1.0", - "@swc/core": "^1.3.62", - "@swc/jest": "^0.2.26", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.5.2", - "@types/node": "^17.0.45", - "@types/react": "^18.2.8", - "@types/react-beautiful-dnd": "^13.1.4", - "@types/react-datepicker": "^4.11.2", - "@types/react-dom": "^18.2.4", - "@typescript-eslint/parser": "^5.59.9", - "@ufb/tsconfig": "*", - "autoprefixer": "^10.4.14", - "eslint": "^8.42.0", - "eslint-config-next": "^13.4.4", - "eslint-config-ufb": "*", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-testing-library": "^5.11.0", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "openapi-typescript": "^6.2.6", - "postcss": "^8.4.24", - "tailwindcss": "^3.3.2", + "@babel/core": "^7.23.2", + "@rollup/plugin-commonjs": "^25.0.7", + "@svgr/webpack": "^8.1.0", + "@swc/core": "^1.3.93", + "@swc/jest": "^0.2.29", + "@testing-library/jest-dom": "^6.1.4", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.5.1", + "@types/jest": "^29.5.6", + "@types/node": "20.8.7", + "@types/react": "^18.2.30", + "@types/react-beautiful-dnd": "^13.1.6", + "@types/react-datepicker": "^4.19.1", + "@types/react-dom": "^18.2.14", + "@ufb/eslint-config": "^0.1.0", + "@ufb/prettier-config": "^0.1.0", + "@ufb/tsconfig": "^0.1.0", + "autoprefixer": "^10.4.16", + "eslint": "^8.51.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "openapi-typescript": "^6.7.0", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.3", "ts-toolbelt": "^9.6.0", - "typescript": "^4.9.5" + "typescript": "^5.2.2" } } diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json index 708178b97..db2b5de3e 100644 --- a/apps/web/public/locales/en/common.json +++ b/apps/web/public/locales/en/common.json @@ -70,11 +70,11 @@ } }, "feedback": { - "title": "Feedback Management", + "title": "Feedback", "column-setting": "Column Setting", "issue-cell": { "issue-list": "Issue list", - "register-issue": "Reigster issue", + "register-issue": "Register issue", "create-issue": "Create issue", "input-issue": "Input issue", "delete-issue": "Delete issue" @@ -101,7 +101,7 @@ } }, "issue": { - "title": "Issue Management", + "title": "Issue", "setting": "Issue Setting", "dialog": { "delete-issue": { @@ -263,7 +263,9 @@ "date": { "today": "Today", "yesterday": "Yesterday", - "before-days": "Before {{day, number}} days" + "before-days": "Before {{day, number}} days", + "date-range-over-max-days": "The inquiry period can only be entered up to {{maxDays}} days", + "all-dates": "The entire period" }, "equal-search": "Equal", "like-search": "Like" @@ -279,4 +281,4 @@ "add": "Addition Complete", "copy": "Copy Complete" } -} \ No newline at end of file +} diff --git a/apps/web/public/locales/ja/common.json b/apps/web/public/locales/ja/common.json index 1e1976a2c..bb8ec839e 100644 --- a/apps/web/public/locales/ja/common.json +++ b/apps/web/public/locales/ja/common.json @@ -68,7 +68,7 @@ } }, "feedback": { - "title": "フィードバック管理", + "title": "フィードバック", "column-setting": "カラムの設定", "issue-cell": { "issue-list": "イシューリスト", @@ -99,7 +99,7 @@ } }, "issue": { - "title": "イシュー管理", + "title": "イシュー", "setting": "イシュー設定", "dialog": { "delete-issue": { @@ -253,7 +253,9 @@ "date": { "today": "今日", "yesterday": "昨日", - "before-days": "{{day, number}}日前" + "before-days": "{{day, number}}日前", + "date-range-over-max-days": "照会期間は最大{{maxDays}}日まで入力可能です。", + "all-dates": "全期間" }, "equal-search": "完全一致", "like-search": "部分一致" diff --git a/apps/web/public/locales/ko/common.json b/apps/web/public/locales/ko/common.json index b94115546..4565b0d14 100644 --- a/apps/web/public/locales/ko/common.json +++ b/apps/web/public/locales/ko/common.json @@ -23,7 +23,7 @@ } }, "reset-password": { - "title": "비밀번호 재설정", + "title": "비밀번호 재설정하기", "button": { "send-email": "이메일 보내기" } } }, @@ -57,18 +57,18 @@ "confirm-new-password": "새로운 비밀번호를 한번 더 입력해주세요" }, "button": { - "delete-account": "계정 삭제" + "delete-account": "회원 탈퇴" }, "dialog": { "delete-account": { - "title": "계정 삭제", - "description1": "정말로 삭제하시나요?", - "description2": "계정 삭제 후 서비스를 이용할 수 없습니다." + "title": "회원 탈퇴", + "description1": "정말로 탈퇴하시나요?", + "description2": "회원 탈퇴 후 서비스를 이용할 수 없습니다." } } }, "feedback": { - "title": "피드백 관리", + "title": "피드백", "column-setting": "컬럼설정", "issue-cell": { "issue-list": "이슈 목록", @@ -99,7 +99,7 @@ } }, "issue": { - "title": "이슈 관리", + "title": "이슈", "setting": "이슈 설정", "dialog": { "delete-issue": { @@ -253,7 +253,9 @@ "date": { "today": "오늘", "yesterday": "어제", - "before-days": "{{day, number}}일전" + "before-days": "{{day, number}}일전", + "date-range-over-max-days": "조회 기간은 최대 {{maxDays}}일까지만 입력 가능합니다.", + "all-dates": "전체 기간" }, "equal-search": "완전 일치", "like-search": "부분 일치" diff --git a/apps/web/src/components/cards/ChannelCard/ChannelCard.tsx b/apps/web/src/components/cards/ChannelCard/ChannelCard.tsx index 57cac7ff2..26583c635 100644 --- a/apps/web/src/components/cards/ChannelCard/ChannelCard.tsx +++ b/apps/web/src/components/cards/ChannelCard/ChannelCard.tsx @@ -13,14 +13,15 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon, IconNameType } from '@ufb/ui'; import { useMemo } from 'react'; -import { ColorType } from '@/types/color.type'; +import type { IconNameType } from '@ufb/ui'; +import { Icon } from '@ufb/ui'; + +import type { ColorType } from '@/types/color.type'; interface IProps extends React.PropsWithChildren { isChecked?: boolean; - onClick?: () => void; value?: React.ReactNode; rightChildren?: React.ReactNode; color: ColorType; @@ -29,10 +30,7 @@ interface IProps extends React.PropsWithChildren { } const ChannelCard: React.FC = (props) => { - const { isChecked, onClick, value, rightChildren, color, iconName, name } = - props; - - const isBtn = useMemo(() => typeof onClick !== 'undefined', [isChecked]); + const { isChecked, value, rightChildren, color, iconName, name } = props; const { bg, icon } = useMemo(() => { switch (color) { @@ -48,16 +46,12 @@ const ChannelCard: React.FC = (props) => { return (
diff --git a/apps/web/src/components/etc/CheckedTableHead/CheckedTableHead.tsx b/apps/web/src/components/etc/CheckedTableHead/CheckedTableHead.tsx index 19fc13417..99c860220 100644 --- a/apps/web/src/components/etc/CheckedTableHead/CheckedTableHead.tsx +++ b/apps/web/src/components/etc/CheckedTableHead/CheckedTableHead.tsx @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Header, flexRender } from '@tanstack/react-table'; +import type { Header } from '@tanstack/react-table'; +import { flexRender } from '@tanstack/react-table'; import { useTranslation } from 'next-i18next'; import DownloadButton from '@/containers/tables/FeedbackTable/DownloadButton'; @@ -52,9 +53,9 @@ const CheckedTableHead: React.FC = (props) => { flexRender(header?.column.columnDef.header, header.getContext())} -
+
-
+
{download && ( <> = (props) => { count={download.ids.length} isHead /> -
+
)} diff --git a/apps/web/src/components/etc/DateRangePicker/DateRangePicker.tsx b/apps/web/src/components/etc/DateRangePicker/DateRangePicker.tsx index 341b105d2..2d466ca38 100644 --- a/apps/web/src/components/etc/DateRangePicker/DateRangePicker.tsx +++ b/apps/web/src/components/etc/DateRangePicker/DateRangePicker.tsx @@ -13,15 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon, Popover, PopoverContent, PopoverTrigger } from '@ufb/ui'; +import { useEffect, useMemo, useState } from 'react'; import { enUS, ja, ko } from 'date-fns/locale'; import dayjs from 'dayjs'; import weekday from 'dayjs/plugin/weekday'; -import { useEffect, useMemo, useState } from 'react'; import ReactDatePicker from 'react-datepicker'; import { useTranslation } from 'react-i18next'; -import { DateRangeType } from '@/types/date-range.type'; +import { Icon, Popover, PopoverContent, PopoverTrigger, toast } from '@ufb/ui'; + +import type { DateRangeType } from '@/types/date-range.type'; dayjs.extend(weekday); @@ -35,6 +36,14 @@ interface IProps extends React.PropsWithChildren { maxDays?: number; isClearable?: boolean; } +const isTotalDateRange = (date: DateRangeType) => { + if (!date) return false; + if (!date.startDate || !date.endDate) return false; + return ( + dayjs(date.startDate).format(DATE_FORMAT) === '1900-01-01' && + dayjs(date.endDate).format(DATE_FORMAT) === '2999-12-31' + ); +}; const DateRangePicker: React.FC = (props) => { const { value, onChange, maxDate, minDate, maxDays, isClearable } = props; @@ -73,25 +82,39 @@ const DateRangePicker: React.FC = (props) => { startDate: dayjs().subtract(90, 'days').toDate(), endDate: dayjs().toDate(), }, + { + label: t('text.date.all-dates'), + startDate: dayjs('1900.01.01').toDate(), + endDate: dayjs('2999.12.31').toDate(), + }, ], [t], ); useEffect(() => { + setActiveIdx(-1); setCurrentValue(value); - }, [value]); + }, [value, isOpen]); const handleChangeDateRange = (index: number, startDate: Date, endDate: Date) => () => { setActiveIdx(index); setCurrentValue({ startDate, endDate }); }; + const handleCancel = () => { setCurrentValue(value); setIsOpen(false); }; + const handleApply = () => { if (!currentValue?.startDate || !currentValue?.endDate) return; + if (maxDays && isOverMaxDays(currentValue, maxDays)) { + toast.negative({ + title: t('text.date.date-range-over-max-days', { maxDays }), + }); + return; + } onChange(currentValue); setIsOpen(false); }; @@ -101,20 +124,28 @@ const DateRangePicker: React.FC = (props) => {
setIsOpen(true)} >

- {value - ? `${dayjs(value?.startDate).format(DATE_FORMAT)} ~ ${dayjs( - value?.endDate, - ).format(DATE_FORMAT)}` + {currentValue + ? isTotalDateRange(currentValue) + ? t('text.date.all-dates') + : `${ + currentValue?.startDate + ? dayjs(currentValue?.startDate).format(DATE_FORMAT) + : '' + } ~ ${ + currentValue?.endDate + ? dayjs(currentValue.endDate).format(DATE_FORMAT) + : '' + }` : 'YYYY-MM-DD ~ YYYY-MM-DD'}

-
+
{isClearable && value && ( = (props) => { {items.map(({ label, startDate, endDate }, index) => (
  • = (props) => { endDate={currentValue?.endDate} monthsShown={2} minDate={minDate} - maxDate={ - maxDays && currentValue?.startDate - ? getMinDate( - dayjs(currentValue.startDate).add(maxDays, 'days').toDate(), - maxDate, - ) - : maxDate - } + maxDate={maxDate} disabledKeyboardNavigation selectsRange inline focusSelectedMonth={false} />
  • -
    +
    @@ -188,10 +212,11 @@ const DateRangePicker: React.FC = (props) => { ); }; -const getMinDate = (a?: Date, b?: Date) => { - if (!a) return b; - if (!b) return a; - return a < b ? a : b; + +const isOverMaxDays = (date: DateRangeType, maxDays: number) => { + if (!date) return false; + if (!date.startDate || !date.endDate) return false; + return dayjs(date.startDate).add(maxDays, 'days').toDate() < date.endDate; }; export default DateRangePicker; diff --git a/apps/web/src/components/etc/Dialog/Dialog_back.tsx b/apps/web/src/components/etc/Dialog/Dialog_back.tsx deleted file mode 100644 index 5ebbaa8fe..000000000 --- a/apps/web/src/components/etc/Dialog/Dialog_back.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -import { - FloatingFocusManager, - FloatingOverlay, - FloatingPortal, - useClick, - useDismiss, - useFloating, - useInteractions, - useRole, -} from '@floating-ui/react'; -import { IIconProps, Icon } from '@ufb/ui'; -import { useId } from 'react'; -import { useTranslation } from 'react-i18next'; - -export interface IDialogProps extends React.PropsWithChildren { - title: string; - description?: string; - open: boolean; - close: () => void; - submitButton?: React.ButtonHTMLAttributes; - icon?: IIconProps; -} - -const Dialog: React.FC = (props) => { - const { title, description, children, open, submitButton, close, icon } = - props; - const { t } = useTranslation(); - - const { refs, context } = useFloating({ - open, - onOpenChange(open) { - if (!open) close(); - }, - }); - - const click = useClick(context); - const dismiss = useDismiss(context, { - outsidePressEvent: 'mousedown', - }); - const role = useRole(context); - - // Merge all the interactions into prop getters - const { getFloatingProps } = useInteractions([click, dismiss, role]); - - // Set up label and description ids - const labelId = useId(); - const descriptionId = useId(); - - return ( - <> - {open && ( - - - -
    -

    - {title} -

    - {icon && ( -
    - -
    - )} - {description && ( -

    - {description} -

    - )} -
    {children}
    -
    - - {submitButton && ( -
    -
    -
    -
    -
    - )} - - ); -}; -export default Dialog; diff --git a/apps/web/src/components/etc/Dialog/PopoverModalContent.tsx b/apps/web/src/components/etc/Dialog/PopoverModalContent.tsx deleted file mode 100644 index cbf01cdd3..000000000 --- a/apps/web/src/components/etc/Dialog/PopoverModalContent.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -import { - IIconProps, - Icon, - PopoverContent, - PopoverHeading, - usePopoverContext, -} from '@ufb/ui'; -import { useTranslation } from 'react-i18next'; - -export interface IDialogProps extends React.PropsWithChildren { - title: string; - description?: string; - icon?: IIconProps; - submitButton: { - children: React.ReactNode; - disabled?: boolean; - className?: string; - onClick?: () => void; - form?: string; - type?: 'submit' | 'reset' | 'button' | undefined; - }; -} - -const PopoverModalContent: React.FC = (props) => { - const { title, description, children, submitButton, icon } = props; - const { t } = useTranslation(); - const { setOpen } = usePopoverContext(); - return ( - - {title} -
    - {icon && ( -
    - -
    - )} - {description && ( -

    - {description} -

    - )} -
    {children}
    -
    - -
    -
    -
    - ); -}; -export default PopoverModalContent; diff --git a/apps/web/src/components/etc/IssueCircle/IssueCircle.tsx b/apps/web/src/components/etc/IssueCircle/IssueCircle.tsx index 98163d846..d7b7b7dd6 100644 --- a/apps/web/src/components/etc/IssueCircle/IssueCircle.tsx +++ b/apps/web/src/components/etc/IssueCircle/IssueCircle.tsx @@ -46,7 +46,7 @@ const IssueCircle: React.FC = ({ issueKey }) => { return (
    diff --git a/apps/web/src/components/etc/OAuthLoginButton/OAuthLoginButton.tsx b/apps/web/src/components/etc/OAuthLoginButton/OAuthLoginButton.tsx new file mode 100644 index 000000000..5bcd53ee1 --- /dev/null +++ b/apps/web/src/components/etc/OAuthLoginButton/OAuthLoginButton.tsx @@ -0,0 +1,53 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { useMemo } from 'react'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'react-i18next'; + +import { useOAIQuery, useTenant } from '@/hooks'; + +interface IProps {} + +const OAuthLoginButton: React.FC = () => { + const { t } = useTranslation(); + const router = useRouter(); + const { tenant } = useTenant(); + + const callback_url = useMemo(() => { + return router.query.callback_url + ? (router.query.callback_url as string) + : ''; + }, [router.query]); + + const { data } = useOAIQuery({ + path: '/api/auth/signIn/oauth/loginURL', + queryOptions: { enabled: tenant?.useOAuth ?? false }, + variables: { callback_url }, + }); + + return ( + + ); +}; + +export default OAuthLoginButton; diff --git a/packages/ufb-ui/src/Dialog/index.ts b/apps/web/src/components/etc/OAuthLoginButton/index.ts similarity index 93% rename from packages/ufb-ui/src/Dialog/index.ts rename to apps/web/src/components/etc/OAuthLoginButton/index.ts index 5108b3923..cd7e61932 100644 --- a/packages/ufb-ui/src/Dialog/index.ts +++ b/apps/web/src/components/etc/OAuthLoginButton/index.ts @@ -13,4 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export * from './Dialog'; +export { default } from './OAuthLoginButton'; diff --git a/apps/web/src/components/etc/PopoverModalContent/PopoverModalContent.tsx b/apps/web/src/components/etc/PopoverModalContent/PopoverModalContent.tsx deleted file mode 100644 index ad12b1879..000000000 --- a/apps/web/src/components/etc/PopoverModalContent/PopoverModalContent.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -import { - IIconProps, - Icon, - PopoverContent, - PopoverHeading, - usePopoverContext, -} from '@ufb/ui'; -import { useTranslation } from 'react-i18next'; - -export interface IDialogProps extends React.PropsWithChildren { - title: string; - description?: string; - icon?: IIconProps; - submitButton: { - children: React.ReactNode; - disabled?: boolean; - className?: string; - onClick?: () => void; - form?: string; - type?: 'submit' | 'reset' | 'button' | undefined; - }; -} - -const PopoverModalContent: React.FC = (props) => { - const { title, description, children, submitButton, icon } = props; - const { t } = useTranslation(); - const { setOpen } = usePopoverContext(); - return ( - - {title} -
    - {icon && ( -
    - -
    - )} - {description && ( -

    - {description} -

    - )} -
    {children}
    -
    - -
    -
    -
    - ); -}; -export default PopoverModalContent; diff --git a/apps/web/src/components/etc/Popper/Popper.tsx b/apps/web/src/components/etc/Popper/Popper.tsx deleted file mode 100644 index c22f1a2bd..000000000 --- a/apps/web/src/components/etc/Popper/Popper.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -import { Popper as MUIPopper, PopperPlacementType } from '@mui/base'; -import React, { Dispatch, SetStateAction, useRef } from 'react'; -import { useClickAway } from 'react-use'; - -interface IProps { - buttonChildren: React.ReactElement; - children?: React.ReactNode; - open: boolean; - setOpen?: Dispatch>; - placement?: PopperPlacementType; - offset?: number; -} - -const Popper: React.FC = (props) => { - const { - buttonChildren, - children, - setOpen, - open, - placement, - offset = 6, - } = props; - const buttonRef = useRef(null); - const containerRef = useRef(null); - - useClickAway(containerRef, () => setOpen && setOpen(false)); - - return ( - <> - {React.cloneElement(buttonChildren, { ref: buttonRef })} - - {children} - - - ); -}; - -export default Popper; diff --git a/apps/web/src/components/etc/SelectBox/SelectBox.tsx b/apps/web/src/components/etc/SelectBox/SelectBox.tsx index 2ca191966..aa58a067d 100644 --- a/apps/web/src/components/etc/SelectBox/SelectBox.tsx +++ b/apps/web/src/components/etc/SelectBox/SelectBox.tsx @@ -13,8 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ +import type { Props } from 'react-select'; +import ReactSelect from 'react-select'; + import { Badge, Icon } from '@ufb/ui'; -import ReactSelect, { Props } from 'react-select'; export type SelectOptionType = | { id?: number; key: any; name: string; [key: string]: any } @@ -68,7 +70,7 @@ function SelectBox(
    diff --git a/apps/web/src/components/etc/SelectBox/SelectBoxCreatable.tsx b/apps/web/src/components/etc/SelectBox/SelectBoxCreatable.tsx index d407df5b4..b5d06a3eb 100644 --- a/apps/web/src/components/etc/SelectBox/SelectBoxCreatable.tsx +++ b/apps/web/src/components/etc/SelectBox/SelectBoxCreatable.tsx @@ -13,9 +13,11 @@ * License for the specific language governing permissions and limitations * under the License. */ +import type { GroupBase } from 'react-select'; +import type { CreatableProps as Props } from 'react-select/creatable'; +import ReactSelect from 'react-select/creatable'; + import { Badge, Icon } from '@ufb/ui'; -import { GroupBase } from 'react-select'; -import ReactSelect, { CreatableProps as Props } from 'react-select/creatable'; export type SelectOptionType = | { id?: number; key: any; name: string; [key: string]: any } @@ -68,7 +70,7 @@ function SelectBoxCreatable(
    diff --git a/apps/web/src/components/etc/SelectBoxWithIcon/SelectBoxWithIcon.tsx b/apps/web/src/components/etc/SelectBoxWithIcon/SelectBoxWithIcon.tsx index 39937e303..746ef9dc5 100644 --- a/apps/web/src/components/etc/SelectBoxWithIcon/SelectBoxWithIcon.tsx +++ b/apps/web/src/components/etc/SelectBoxWithIcon/SelectBoxWithIcon.tsx @@ -15,10 +15,8 @@ */ import { components } from 'react-select'; -import SelectBox, { - ISelectBoxProps, - SelectOptionType, -} from '../SelectBox/SelectBox'; +import type { ISelectBoxProps, SelectOptionType } from '../SelectBox/SelectBox'; +import SelectBox from '../SelectBox/SelectBox'; interface IProps extends ISelectBoxProps { SingleValue?: { diff --git a/apps/web/src/components/etc/ShareButton/ShareButton.tsx b/apps/web/src/components/etc/ShareButton/ShareButton.tsx index 2ed600567..7447f1969 100644 --- a/apps/web/src/components/etc/ShareButton/ShareButton.tsx +++ b/apps/web/src/components/etc/ShareButton/ShareButton.tsx @@ -13,23 +13,23 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon, toast } from '@ufb/ui'; -import { MouseEventHandler } from 'react'; +import type { MouseEventHandler } from 'react'; import { useTranslation } from 'react-i18next'; +import { Icon, toast } from '@ufb/ui'; + interface IProps { - id: number; pathname: string; } -const ShareButton: React.FC = ({ id, pathname }) => { +const ShareButton: React.FC = ({ pathname }) => { const { t } = useTranslation(); const onClickLinkCopy: MouseEventHandler = (e) => { e.stopPropagation(); try { const { origin } = window.location; - navigator.clipboard.writeText(`${origin}${pathname}?id=${id}`); + navigator.clipboard.writeText(`${origin}${pathname}`); toast.positive({ title: t('toast.copy'), iconName: 'CopyFill' }); } catch (error) { toast.negative({ title: 'fail' }); diff --git a/apps/web/src/components/etc/TableCheckbox/TableCheckbox.tsx b/apps/web/src/components/etc/TableCheckbox/TableCheckbox.tsx index f5bc9af30..d2d607b5b 100644 --- a/apps/web/src/components/etc/TableCheckbox/TableCheckbox.tsx +++ b/apps/web/src/components/etc/TableCheckbox/TableCheckbox.tsx @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { HTMLProps, useEffect, useRef } from 'react'; +import type { HTMLProps } from 'react'; +import { useEffect, useRef } from 'react'; interface IProps extends HTMLProps { indeterminate?: boolean; diff --git a/apps/web/src/components/etc/TableLoadingRow/TableLoadingRow.tsx b/apps/web/src/components/etc/TableLoadingRow/TableLoadingRow.tsx index 692aee3e8..c95e075c0 100644 --- a/apps/web/src/components/etc/TableLoadingRow/TableLoadingRow.tsx +++ b/apps/web/src/components/etc/TableLoadingRow/TableLoadingRow.tsx @@ -21,8 +21,8 @@ const TableLoadingRow: React.FC = ({ colSpan }) => { return ( -
    -
    +
    +
    diff --git a/apps/web/src/components/etc/TablePagination/TablePagination.tsx b/apps/web/src/components/etc/TablePagination/TablePagination.tsx index 2ff7962b8..ac8a2fb19 100644 --- a/apps/web/src/components/etc/TablePagination/TablePagination.tsx +++ b/apps/web/src/components/etc/TablePagination/TablePagination.tsx @@ -13,9 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon } from '@ufb/ui'; import { useTranslation } from 'next-i18next'; +import { Icon } from '@ufb/ui'; + interface IProps extends React.PropsWithChildren { limit?: number; setLimit?: (limit: number) => void; diff --git a/apps/web/src/components/etc/TableResizer/TableResizer.tsx b/apps/web/src/components/etc/TableResizer/TableResizer.tsx new file mode 100644 index 000000000..c0eea578b --- /dev/null +++ b/apps/web/src/components/etc/TableResizer/TableResizer.tsx @@ -0,0 +1,51 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import type { Header, Table } from '@tanstack/react-table'; + +import { Icon } from '@ufb/ui'; + +interface IProps { + table: Table; + header: Header; +} + +const TableResizer: React.FC = ({ table, header }) => { + return ( +
    + +
    + ); +}; + +export default TableResizer; diff --git a/apps/web/src/components/etc/Tooltip/index.ts b/apps/web/src/components/etc/TableResizer/index.ts similarity index 93% rename from apps/web/src/components/etc/Tooltip/index.ts rename to apps/web/src/components/etc/TableResizer/index.ts index e8abe49c8..1a75c64e1 100644 --- a/apps/web/src/components/etc/Tooltip/index.ts +++ b/apps/web/src/components/etc/TableResizer/index.ts @@ -13,4 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export { default } from './Tooltip'; +export { default } from './TableResizer'; diff --git a/apps/web/src/components/etc/TableSearchInput/TableSearchInput.tsx b/apps/web/src/components/etc/TableSearchInput/TableSearchInput.tsx index 27e875b92..3d3f0a9d1 100644 --- a/apps/web/src/components/etc/TableSearchInput/TableSearchInput.tsx +++ b/apps/web/src/components/etc/TableSearchInput/TableSearchInput.tsx @@ -13,21 +13,21 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { useEffect, useMemo, useRef, useState } from 'react'; import { Combobox } from '@headlessui/react'; -import { Badge, Icon } from '@ufb/ui'; import { useTranslation } from 'next-i18next'; -import { useEffect, useMemo, useRef, useState } from 'react'; -import { PrimitiveFieldFormatEnumType } from '@/types/field.type'; -import { removeEmptyValueInObject } from '@/utils/remove-empty-value-in-object'; +import { Badge, Icon } from '@ufb/ui'; -import TableSearchInputPopover from './TableSearchInputPopover'; +import type { PrimitiveFieldFormatEnumType } from '@/types/field.type'; +import { removeEmptyValueInObject } from '@/utils/remove-empty-value-in-object'; import { objToQuery, objToStr, strToObj, strValueToObj, } from './table-search-input.service'; +import TableSearchInputPopover from './TableSearchInputPopover'; export type SearchItemType = { key: string; @@ -45,14 +45,14 @@ export type SearchItemType = { interface IProps { onChangeQuery: (query: Record) => void; searchItems: SearchItemType[]; - initialQuery?: Record; + query?: Record; defaultQuery?: Record; } const TableSearchInput: React.FC = ({ onChangeQuery, searchItems, - initialQuery, + query, }) => { const inputRef = useRef(null); const popoverRef = useRef(null); @@ -61,7 +61,7 @@ const TableSearchInput: React.FC = ({ const [inputValue, setInputValue] = useState(''); const editingName = useMemo(() => { - if (!inputRef.current || !inputRef.current.selectionEnd) return; + if (!inputRef.current || !inputRef.current.selectionEnd) return ''; const { selectionEnd } = inputRef.current; const targetInput = inputValue.slice(0, selectionEnd); @@ -73,7 +73,7 @@ const TableSearchInput: React.FC = ({ startIndex === endIndex ? inputValue.length : endIndex, ); return value.indexOf(':') === -1 - ? null + ? '' : value.slice(0, value.indexOf(':')).trim(); }, [inputValue, inputRef]); @@ -98,9 +98,10 @@ const TableSearchInput: React.FC = ({ ); useEffect(() => { - if (!initialQuery) return; - setInputValue(objToStr(initialQuery, searchItems)); - }, [searchItems]); + if (!query) return; + const inputValue = objToStr(query, searchItems); + setInputValue(inputValue); + }, [searchItems, query]); const filterIconCN = useMemo( () => (isOpenPopover ? 'text-primary' : 'text-tertiary'), @@ -115,10 +116,14 @@ const TableSearchInput: React.FC = ({ ? removeEmptyValueInObject({ ...currentObj, ...inputObject }) : {}; - setInputValue(objToStr(currentQuery, searchItems)); - onChangeQuery( - removeEmptyValueInObject(objToQuery(currentQuery, searchItems)), + const inputText = objToStr(currentQuery, searchItems); + setInputValue(inputText); + + const newQUery = removeEmptyValueInObject( + objToQuery(currentQuery, searchItems), ); + + onChangeQuery(newQUery); }; const reset = () => { @@ -129,7 +134,7 @@ const TableSearchInput: React.FC = ({ return ( = ({ /> setInputValue(e.currentTarget.value)} value={inputValue} displayValue={() => inputValue} onFocus={() => close()} onKeyDown={(e) => { - if (e.key === 'Enter' && (e.target as any).value.length === 0) + if (e.key === 'Enter' && (e.target as any).value.length === 0) { onInputChangeQuery({}); + } }} placeholder=" " /> {inputValue.length > 0 && ( )} - - {!editingName + + {editingName === '' ? searchItems .filter( (v) => @@ -205,7 +211,7 @@ const TableSearchInput: React.FC = ({ {isOpenPopover && (
    = ({ - ['p-3 cursor-pointer', active ? 'bg-secondary' : 'bg-primary'].join(' ') + ['cursor-pointer p-3', active ? 'bg-secondary' : 'bg-primary'].join(' ') } value={{ [key]: strValueToObj(editingValue, searchItem) }} > diff --git a/apps/web/src/components/etc/TableSearchInput/TableSearchInputPopover.tsx b/apps/web/src/components/etc/TableSearchInput/TableSearchInputPopover.tsx index 5aeacbb73..59a324f6e 100644 --- a/apps/web/src/components/etc/TableSearchInput/TableSearchInputPopover.tsx +++ b/apps/web/src/components/etc/TableSearchInput/TableSearchInputPopover.tsx @@ -13,16 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon } from '@ufb/ui'; -import produce from 'immer'; -import { useTranslation } from 'next-i18next'; import { useCallback, useState } from 'react'; +import { produce } from 'immer'; +import { useTranslation } from 'next-i18next'; -import { DateRangeType } from '@/types/date-range.type'; +import { Icon } from '@ufb/ui'; +import type { DateRangeType } from '@/types/date-range.type'; import DateRangePicker from '../DateRangePicker'; import SelectBox from '../SelectBox'; -import { SearchItemType } from './TableSearchInput'; +import type { SearchItemType } from './TableSearchInput'; const BooleanOptions = [ { name: 'True', key: true }, @@ -84,7 +84,7 @@ const TableSearchInputPopover: React.FC = (props) => { return (
    -
    +

    {t('text.search-filter')}

    {columns.map((item) => ( -
    +
    {item.format === 'date' && (

    {item.name}

    diff --git a/apps/web/src/components/etc/TableSearchInput/table-search-input.service.ts b/apps/web/src/components/etc/TableSearchInput/table-search-input.service.ts index 9033a63ec..8efd726c4 100644 --- a/apps/web/src/components/etc/TableSearchInput/table-search-input.service.ts +++ b/apps/web/src/components/etc/TableSearchInput/table-search-input.service.ts @@ -16,8 +16,7 @@ import dayjs from 'dayjs'; import { DATE_FORMAT } from '@/constants/dayjs-format'; - -import { SearchItemType } from './TableSearchInput'; +import type { SearchItemType } from './TableSearchInput'; export const strToObj = (input: string, searchItems: SearchItemType[]) => { const splitValues = input.split(','); @@ -30,6 +29,7 @@ export const strToObj = (input: string, searchItems: SearchItemType[]) => { for (const mergedValue of mergedValues) { const [name, value] = mergedValue.split(':').map((v) => v.trim()); + if (!name || !value) continue; const column = searchItems.find((column) => column.name === name); @@ -47,11 +47,12 @@ export const strValueToObj = (value: string, searchItems: SearchItemType) => { switch (searchItems.format) { case 'boolean': return strToBoolean(value); - case 'date': + case 'date': { const [gte, lt] = value .split('~') .map((v) => dayjs(v, { format: DATE_FORMAT }).toDate()); return { gte, lt }; + } case 'issue': case 'issue_status': case 'select': @@ -70,7 +71,7 @@ export const objToStr = ( .map(([key, value]) => { const column = searchItems.find((column) => column.key === key); - if (!column) return ``; + if (!column) return; const { name } = column; switch (column.format) { @@ -87,10 +88,19 @@ export const objToStr = ( return ''; } case 'issue': - const issueName = Array.isArray(value) - ? column.options?.find((v) => v.id === value[0])?.name - : value?.name ?? value; - return `${name}:${issueName}`; + if (Array.isArray(value)) { + const issueName = column.options?.find((v) => v.id === value[0]) + ?.name; + return `${name}:${issueName}`; + } else if (value?.name) { + const issueName = value?.name; + return `${name}:${issueName}`; + } else { + const issueName = + column.options?.find((v) => v.id === parseInt(value))?.name ?? + value; + return `${name}:${issueName}`; + } case 'issue_status': case 'select': case 'multiSelect': @@ -99,6 +109,7 @@ export const objToStr = ( return `${name}:${value}`; } }) + .filter((v) => !!v) .join(','); }; @@ -137,29 +148,32 @@ export const objToQuery = ( }; } break; - case 'issue_status': + case 'issue_status': { const statusId = value?.id ?? column.options?.find((v) => v.name === value)?.key; result[key] = statusId; break; - case 'issue': + } + case 'issue': { const issueId = value?.id ?? column.options?.find((v) => v.name === value)?.id; result[key] = [issueId]; break; - case 'multiSelect': + } + case 'multiSelect': { const optionKey1 = value?.key ?? column.options?.find((v) => v.name === value)?.key; if (!optionKey1) break; result[key] = [optionKey1]; break; - case 'select': + } + case 'select': { const optionKey2 = value?.key ?? column.options?.find((v) => v.name === value)?.key; if (!optionKey2) break; result[key] = optionKey2; break; - + } default: result[key] = value; break; diff --git a/apps/web/src/components/etc/TableSortIcon/TableSortIcon.tsx b/apps/web/src/components/etc/TableSortIcon/TableSortIcon.tsx index 2f5242b26..0bdbf87df 100644 --- a/apps/web/src/components/etc/TableSortIcon/TableSortIcon.tsx +++ b/apps/web/src/components/etc/TableSortIcon/TableSortIcon.tsx @@ -13,7 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Column } from '@tanstack/react-table'; +import type { Column } from '@tanstack/react-table'; + import { Icon } from '@ufb/ui'; interface IProps extends React.PropsWithChildren { diff --git a/apps/web/src/components/etc/Tooltip/Tooltip.tsx b/apps/web/src/components/etc/Tooltip/Tooltip.tsx deleted file mode 100644 index 030d4deba..000000000 --- a/apps/web/src/components/etc/Tooltip/Tooltip.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -import { Icon, Popover, PopoverContent, PopoverTrigger } from '@ufb/ui'; -import { useState } from 'react'; - -interface IProps { - title: string; - iconSize?: number; -} - -const Tooltip: React.FC = ({ title, iconSize = 14 }) => { - const [open, setOpen] = useState(false); - - return ( - - - setOpen(true)} - onMouseOut={() => setOpen(false)} - /> - - -

    {title}

    -
    -
    - ); -}; - -export default Tooltip; diff --git a/apps/web/src/components/etc/index.ts b/apps/web/src/components/etc/index.ts index deb236e49..788e548b8 100644 --- a/apps/web/src/components/etc/index.ts +++ b/apps/web/src/components/etc/index.ts @@ -13,19 +13,19 @@ * License for the specific language governing permissions and limitations * under the License. */ -export { default as ExpandableText } from './ExpandableText'; export { default as TableSearchInput } from './TableSearchInput'; export { default as TableSortIcon } from './TableSortIcon'; -export { default as CheckedTableHead } from './CheckedTableHead'; export { default as TablePagination } from './TablePagination'; export { default as TableCheckbox } from './TableCheckbox'; -export { default as SelectBox } from './SelectBox'; +export { default as TableLoadingRow } from './TableLoadingRow'; +export { default as TableResizer } from './TableResizer'; +export { default as ExpandableText } from './ExpandableText'; +export { default as CheckedTableHead } from './CheckedTableHead'; + export { default as ShareButton } from './ShareButton'; -export { default as Popper } from './Popper'; export { default as DateRangePicker } from './DateRangePicker'; -export { default as Tooltip } from './Tooltip'; export { default as SelectBoxWithIcon } from './SelectBoxWithIcon'; -export { default as TableLoadingRow } from './TableLoadingRow'; export { default as IssueCircle } from './IssueCircle'; -export { default as Dialog } from './Dialog'; -export { default as PopoverModalContent } from './PopoverModalContent'; +export { default as OAuthLoginButton } from './OAuthLoginButton'; + +export { default as SelectBox } from './SelectBox'; diff --git a/apps/web/src/components/layouts/Header/Header.tsx b/apps/web/src/components/layouts/Header/Header.tsx index 57b000fee..ebaabc512 100644 --- a/apps/web/src/components/layouts/Header/Header.tsx +++ b/apps/web/src/components/layouts/Header/Header.tsx @@ -13,16 +13,16 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon } from '@ufb/ui'; +import { useEffect } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useEffect } from 'react'; import { useStore } from 'zustand'; +import { Icon } from '@ufb/ui'; + import { Path } from '@/constants/path'; import themeStore from '@/zustand/theme.store'; - import HeaderName from './HeaderName'; import LocaleSelectBox from './LocaleSelectBox'; import ProfileBox from './ProfileBox'; @@ -38,10 +38,10 @@ const Header: React.FC = () => { }, [theme]); return ( -
    +
    { width={24} height={24} /> - +
    diff --git a/apps/web/src/components/layouts/Header/HeaderName.tsx b/apps/web/src/components/layouts/Header/HeaderName.tsx index 914ffc9b5..53b0d2975 100644 --- a/apps/web/src/components/layouts/Header/HeaderName.tsx +++ b/apps/web/src/components/layouts/Header/HeaderName.tsx @@ -13,9 +13,10 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon } from '@ufb/ui'; -import { useRouter } from 'next/router'; import { useMemo } from 'react'; +import { useRouter } from 'next/router'; + +import { Icon } from '@ufb/ui'; import { useOAIQuery, useTenant } from '@/hooks'; @@ -39,22 +40,29 @@ const HeaderName: React.FC = () => { }); return ( - <> +
    -
    +
    {tenant?.siteName}
    {data && ( -
    -
    - + <> + +
    +
    + +
    + {data?.name}
    - {data?.name} -
    + )} - +
    ); }; diff --git a/apps/web/src/components/layouts/Header/LocaleSelectBox.tsx b/apps/web/src/components/layouts/Header/LocaleSelectBox.tsx index 96d5c415b..ed49152e2 100644 --- a/apps/web/src/components/layouts/Header/LocaleSelectBox.tsx +++ b/apps/web/src/components/layouts/Header/LocaleSelectBox.tsx @@ -13,11 +13,12 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { useCallback } from 'react'; +import { useRouter } from 'next/router'; import { Listbox } from '@headlessui/react'; -import { Icon } from '@ufb/ui'; import { setCookie } from 'cookies-next'; -import { useRouter } from 'next/router'; -import { useCallback } from 'react'; + +import { Icon } from '@ufb/ui'; interface IProps extends React.PropsWithChildren {} @@ -39,7 +40,7 @@ const LocaleSelectBox: React.FC = () => { value={router.locale} onChange={(v) => onToggleLanguageClick(v)} > - + {({ value }) => ( <> @@ -47,7 +48,7 @@ const LocaleSelectBox: React.FC = () => { )} - + {router.locales ?.filter((v) => v !== 'default') .map((v) => ( @@ -56,7 +57,7 @@ const LocaleSelectBox: React.FC = () => { value={v} className={({ selected }) => [ - 'select-none p-2 cursor-pointer hover:bg-secondary uppercase text-center font-extrabold', + 'hover:bg-secondary cursor-pointer select-none p-2 text-center font-extrabold uppercase', selected ? 'font-bold' : 'font-normal', ].join(' ') } diff --git a/apps/web/src/components/layouts/Header/ProfileBox.tsx b/apps/web/src/components/layouts/Header/ProfileBox.tsx index 74f1cddc4..5474e4e4f 100644 --- a/apps/web/src/components/layouts/Header/ProfileBox.tsx +++ b/apps/web/src/components/layouts/Header/ProfileBox.tsx @@ -13,11 +13,12 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon, Popover, PopoverContent, PopoverTrigger } from '@ufb/ui'; -import { useRouter } from 'next/router'; import { Fragment, useState } from 'react'; +import { useRouter } from 'next/router'; import { useTranslation } from 'react-i18next'; +import { Icon, Popover, PopoverContent, PopoverTrigger } from '@ufb/ui'; + import { useUser } from '@/hooks'; interface IProps {} @@ -34,7 +35,7 @@ const ProfileBox: React.FC = () => { setOpen((v) => !v)}>
    @@ -47,7 +48,7 @@ const ProfileBox: React.FC = () => {
    • { router.push('/main/profile'); setOpen(false); @@ -56,7 +57,7 @@ const ProfileBox: React.FC = () => { {t('header.profile')}
    • { signOut(); setOpen(false); diff --git a/apps/web/src/components/layouts/SideNav/SideNav.tsx b/apps/web/src/components/layouts/SideNav/SideNav.tsx index b9681e7d7..032676ec3 100644 --- a/apps/web/src/components/layouts/SideNav/SideNav.tsx +++ b/apps/web/src/components/layouts/SideNav/SideNav.tsx @@ -13,12 +13,14 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Icon, IconNameType } from '@ufb/ui'; +import type { UrlObject } from 'url'; +import { useRef, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { UrlObject } from 'url'; + +import type { IconNameType } from '@ufb/ui'; +import { Icon } from '@ufb/ui'; import { Path } from '@/constants/path'; import { useCurrentProjectId, usePermissions } from '@/hooks'; @@ -27,7 +29,6 @@ interface IProps extends React.PropsWithChildren {} const SideNav: React.FC = () => { const { t } = useTranslation(); - const router = useRouter(); const { projectId } = useCurrentProjectId(); const perms = usePermissions(projectId); @@ -37,20 +38,20 @@ const SideNav: React.FC = () => { return (