Skip to content

Commit

Permalink
Merge pull request #606 from line/dev
Browse files Browse the repository at this point in the history
release: 6.2432.62
  • Loading branch information
jihun authored Aug 7, 2024
2 parents 787f047 + 20cb789 commit 15bbfdb
Show file tree
Hide file tree
Showing 163 changed files with 4,114 additions and 2,519 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

services:
mysql:
image: mysql:9.0.0
image: mysql:9.0.1
env:
MYSQL_ROOT_PASSWORD: userfeedback
MYSQL_DATABASE: e2e
Expand Down Expand Up @@ -39,7 +39,7 @@ jobs:

- name: Build and run
run: |
docker-compose -f "./docker/docker-compose.e2e.yml" up -d
docker compose -f "./docker/docker-compose.e2e.yml" up -d
- name: Setup e2e test
run: |
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.15.1
20.16.0
25 changes: 25 additions & 0 deletions apps/api/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const baseConfig = require('@ufb/eslint-config/base');
const nestjsConfig = require('@ufb/eslint-config/nestjs');

const tsParser = require('@typescript-eslint/parser');
const globals = require('globals');

module.exports = [
{
ignores: ['dist/**', '**/*.js'],
},
...baseConfig,
...nestjsConfig,
{
languageOptions: {
globals: { ...globals.node, ...globals.jest },
parser: tsParser,
ecmaVersion: 5,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
},
},
},
];
15 changes: 15 additions & 0 deletions apps/api/jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/**
* 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.
*/
jest.mock('typeorm-transactional', () => ({
Transactional: () => () => ({}),
initializeTransactionalContext: () => {},
Expand Down
1 change: 1 addition & 0 deletions apps/api/nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"builder": "swc",
"deleteOutDir": true,
"assets": ["**/*.hbs"],
"watchAssets": true
Expand Down
16 changes: 10 additions & 6 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "api",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "nest build",
"clean": "git clean -xdf dist .turbo node_modules",
Expand All @@ -28,7 +27,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.556.0",
"@aws-sdk/s3-request-presigner": "^3.556.0",
"@fastify/multipart": "^8.2.0",
"@fastify/multipart": "^8.3.0",
"@fastify/static": "^7.0.3",
"@nestjs-modules/mailer": "^2.0.0",
"@nestjs/axios": "^3.0.2",
Expand All @@ -45,7 +44,10 @@
"@nestjs/terminus": "^10.2.3",
"@nestjs/typeorm": "^10.0.2",
"@opensearch-project/opensearch": "^2.7.0",
"@swc/cli": "^0.4.0",
"@swc/helpers": "^0.5.10",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"@ufb/shared": "workspace:*",
"@willsoto/nestjs-prometheus": "^6.0.0",
"aws-sdk": "^2.1604.0",
Expand All @@ -62,7 +64,7 @@
"luxon": "^3.4.4",
"magic-bytes.js": "^1.10.0",
"mysql2": "^3.9.7",
"nestjs-cls": "^3.6.0",
"nestjs-cls": "^4.0.0",
"nestjs-pino": "^4.0.0",
"nestjs-typeorm-paginate": "^4.0.4",
"nodemailer": "^6.9.13",
Expand Down Expand Up @@ -92,13 +94,15 @@
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/luxon": "^3.4.2",
"@types/node": "20.14.11",
"@types/node": "20.14.14",
"@types/nodemailer": "^6.4.15",
"@types/passport-jwt": "*",
"@types/supertest": "^6.0.2",
"@ufb/prettier-config": "workspace:*",
"@ufb/tsconfig": "workspace:*",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"@ufb/eslint-config": "workspace:*",
"@ufb/prettier-config": "workspace:*",
"@ufb/tsconfig": "workspace:*",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
Expand Down
9 changes: 6 additions & 3 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { Request } from 'express';
import { ClsModule } from 'nestjs-cls';
import { LoggerModule } from 'nestjs-pino';

Expand Down Expand Up @@ -80,7 +81,7 @@ export const domainModules = [
FeedbackIssueStatisticsModule,
APIModule,
SchedulerLockModule,
] as any[];
] as (typeof AuthModule)[];

@Module({
imports: [
Expand All @@ -102,15 +103,17 @@ export const domainModules = [
pinoHttp: {
transport: { target: 'pino-pretty', options: { singleLine: true } },
autoLogging: {
ignore: (req: any) => req.originalUrl === '/api/health',
ignore: (req: Request) => 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) {
} else if (res.statusCode >= 500) {
return 'error';
} else if (err != null) {
return 'error';
}
return 'info';
Expand Down
20 changes: 14 additions & 6 deletions apps/api/src/common/decorators/dto-validator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,24 @@ class Dto {

class TestClass {
@DtoValidator()
noParam() {}
noParam() {
return;
}

@DtoValidator()
dtoParam(_dto: Dto) {}
dtoParam(_dto: Dto) {
return;
}

@DtoValidator()
dtosParam(_dtos: Dto[]) {}
dtosParam(_dtos: Dto[]) {
return;
}

@DtoValidator()
compositionParam(_a: any, _dtos: Dto) {}
compositionParam(_a: any, _dtos: Dto) {
return;
}
}
describe('dto validator', () => {
let instance: TestClass;
Expand All @@ -49,14 +57,14 @@ describe('dto validator', () => {
dto.str = 'test';
instance.dtoParam(dto);
const dto2 = new Dto();
expect(instance.dtoParam(dto2)).rejects.toThrow();
void expect(instance.dtoParam(dto2)).rejects.toThrow();
});
it('method call with no params', () => {
const dto = new Dto();
dto.str = 'test';
instance.dtosParam([dto]);
const dto2 = new Dto();
expect(instance.dtosParam([dto2])).rejects.toThrow();
void expect(instance.dtosParam([dto2])).rejects.toThrow();
});
it('method call with no params', () => {
const dto = new Dto();
Expand Down
22 changes: 16 additions & 6 deletions apps/api/src/common/decorators/dto-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,23 @@
* under the License.
*/
import { InternalServerErrorException } from '@nestjs/common';
import type { ValidationError } from 'class-validator';
import { validate } from 'class-validator';

type Method = (...args: object[]) => any;

const DtoValidator =
() => (target: unknown, propName: string, descriptor: any) => {
const methodRef = (descriptor as any).value;
() =>
(
target: unknown,
propName: string,
descriptor: TypedPropertyDescriptor<any>,
) => {
const methodRef = descriptor.value as Method;

(descriptor as any).value = async function (...args) {
descriptor.value = async function (...args: object[]): Promise<any> {
for (const arg of args) {
let errors = [];
let errors: ValidationError[] = [];

if (!Array.isArray(arg) && typeof arg === 'object') {
errors = await validate(arg);
Expand All @@ -32,14 +40,16 @@ const DtoValidator =
typeof arg[0] === 'object'
) {
errors = (
await Promise.all(arg.map(async (item) => await validate(item)))
await Promise.all(
arg.map(async (item: object) => await validate(item)),
)
).flat();
}
if (errors.length > 0) {
throw new InternalServerErrorException(errors);
}
}
return await methodRef.call(this, ...args);
return (await methodRef.call(this, ...args)) as object;
};
return descriptor;
};
Expand Down
18 changes: 9 additions & 9 deletions apps/api/src/common/decorators/is-password.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* under the License.
*/
import { faker } from '@faker-js/faker';
import { Validator } from 'class-validator';
import { ValidationError, Validator } from 'class-validator';

import { IsPassword } from './is-password';

Expand All @@ -30,7 +30,7 @@ describe('IsPassword decorator', () => {
faker.number.int({ min: 8, max: 15 }),
);
const validator = new Validator();
validator.validate(instance).then((errors) => {
void validator.validate(instance).then((errors) => {
expect(errors).toHaveLength(0);
});
});
Expand All @@ -40,9 +40,9 @@ describe('IsPassword decorator', () => {
faker.number.int({ min: 0, max: 7 }),
);
const validator = new Validator();
validator.validate(instance).then((errors) => {
void validator.validate(instance).then((errors: ValidationError[]) => {
expect(errors.length).toEqual(1);
expect(Object.keys(errors[0].constraints)[0]).toEqual('minLength');
expect(Object.keys(errors[0].constraints ?? {})[0]).toEqual('minLength');
});
});

Expand All @@ -51,20 +51,20 @@ describe('IsPassword decorator', () => {
instance.password = faker.number.int({ min: 10000000, max: 99999999 });

const validator = new Validator();
validator.validate(instance).then((errors) => {
void validator.validate(instance).then((errors: ValidationError[]) => {
expect(errors.length).toEqual(1);
expect(Object.keys(errors[0].constraints)[0]).toEqual('isString');
expect(Object.keys(errors[0].constraints ?? {})[0]).toEqual('isString');
});
});

it('isString, minLength', () => {
const instance = new IsPasswordTest();
instance.password = faker.number.int({ min: 0, max: 9999999 });
const validator = new Validator();
validator.validate(instance).then((errors) => {
void validator.validate(instance).then((errors: ValidationError[]) => {
expect(errors.length).toEqual(1);
expect(Object.keys(errors[0].constraints)[0]).toEqual('isString');
expect(Object.keys(errors[0].constraints)[1]).toEqual('minLength');
expect(Object.keys(errors[0].constraints ?? {})[0]).toEqual('isString');
expect(Object.keys(errors[0].constraints ?? {})[1]).toEqual('minLength');
});
});
});
8 changes: 6 additions & 2 deletions apps/api/src/common/dtos/pagination-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ import { IsNumber, IsOptional, Min } from 'class-validator';
import { toNumber } from '@/common/helper/cast.helper';

export class PaginationRequestDto {
@Transform(({ value }) => toNumber(value, { default: 10, min: 1 }))
@Transform(({ value }: { value: string }) =>
toNumber(value, { default: 10, min: 1 }),
)
@ApiProperty({ required: false, minimum: 1, default: 10, example: 10 })
@IsOptional()
@IsNumber()
@Min(1)
limit: number;

@Transform(({ value }) => toNumber(value, { default: 1, min: 1 }))
@Transform(({ value }: { value: string }) =>
toNumber(value, { default: 1, min: 1 }),
)
@ApiProperty({ required: false, minimum: 1, default: 1, example: 1 })
@IsOptional()
@IsNumber()
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/common/entities/common.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export abstract class CommonEntity {

@BeforeInsert()
beforeInsertHook() {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!this.createdAt) {
this.createdAt = DateTime.utc().toJSDate();
}
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/common/filters/http-exception.filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ export class HttpExceptionFilter implements ExceptionFilter {
const exceptionResponse = exception.getResponse();

if (typeof exceptionResponse === 'string') {
response.status(statusCode).send({
void response.status(statusCode).send({
response: exceptionResponse,
path: request.url,
});
} else {
response.status(statusCode).send({
void response.status(statusCode).send({
...exceptionResponse,
statusCode,
path: request.url,
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/common/helper/cast.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ export function toNumber(value: string, opts: ToNumberOptions = {}): number {
let newValue: number = Number.parseInt(value || String(opts.default), 10);

if (Number.isNaN(newValue)) {
newValue = opts.default;
newValue = opts.default ?? 0;
}

if (opts.min) {
if (newValue < opts.min) {
newValue = opts.min;
}

if (newValue > opts.max) {
if (opts.max && newValue > opts.max) {
newValue = opts.max;
}
}
Expand Down
Loading

0 comments on commit 15bbfdb

Please sign in to comment.