Skip to content

Commit

Permalink
Merge pull request #182 from line/dev
Browse files Browse the repository at this point in the history
release: 4.2407.38
  • Loading branch information
h4l-yup authored Feb 19, 2024
2 parents 6cb0276 + c67ffdc commit 94cb008
Show file tree
Hide file tree
Showing 35 changed files with 294 additions and 330 deletions.
7 changes: 4 additions & 3 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@nestjs/common": "^10.2.7",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.2.7",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/jwt": "^10.1.1",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.2.7",
Expand All @@ -45,7 +46,7 @@
"@ufb/shared": "^0.1.0",
"@willsoto/nestjs-prometheus": "^6.0.0",
"aws-sdk": "^2.1509.0",
"axios": "^1.6.0",
"axios": "^1.6.7",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
Expand All @@ -57,12 +58,12 @@
"nestjs-pino": "^3.5.0",
"nestjs-typeorm-paginate": "^4.0.4",
"nodemailer": "^6.9.7",
"passport": "^0.6.0",
"passport": "^0.7.0",
"passport-custom": "^1.1.1",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pino-http": "^8.5.0",
"pino-pretty": "^10.2.3",
"pino-pretty": "^10.3.1",
"prom-client": "^15.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import { ClsModule } from 'nestjs-cls';
Expand Down Expand Up @@ -117,6 +118,7 @@ export const domainModules = [
middleware: { mount: true },
}),
ScheduleModule.forRoot(),
EventEmitterModule.forRoot(),
...domainModules,
],
})
Expand Down
1 change: 0 additions & 1 deletion apps/api/src/common/enums/field-format.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export enum FieldFormatEnum {
text = 'text',
keyword = 'keyword',
number = 'number',
boolean = 'boolean',
select = 'select',
multiSelect = 'multiSelect',
date = 'date',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* 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 { MigrationInterface, QueryRunner } from 'typeorm';

export class DeprecateBooleanField1707356935078 implements MigrationInterface {
name = 'DeprecateBooleanField1707356935078';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`fields\` CHANGE \`format\` \`format\` enum ('text', 'keyword', 'number', 'select', 'multiSelect', 'date', 'image') NOT NULL`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`fields\` CHANGE \`format\` \`format\` enum ('text', 'keyword', 'number', 'boolean', 'select', 'multiSelect', 'date', 'image') NOT NULL`,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* 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 { MigrationInterface, QueryRunner } from 'typeorm';

export class CodeVerificationTryCount1707979877289
implements MigrationInterface
{
name = 'CodeVerificationTryCount1707979877289';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`codes\` ADD \`try_count\` int NOT NULL DEFAULT '0'`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE \`codes\` DROP COLUMN \`try_count\``);
}
}
1 change: 0 additions & 1 deletion apps/api/src/domains/admin/channel/field/field.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const FIELD_TYPES_TO_MAPPING_TYPES: Record<FieldFormatEnum, string> = {
text: 'text',
keyword: 'keyword',
number: 'integer',
boolean: 'boolean',
select: 'keyword',
multiSelect: 'keyword',
date: 'date',
Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/domains/admin/feedback/feedback.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export function isInvalidSortMethod(method: SortMethodEnum) {

export function validateValue(field: FieldEntity, value: any) {
switch (field.format) {
case FieldFormatEnum.boolean:
return typeof value === 'boolean';
case FieldFormatEnum.number:
return typeof value === 'number';
case FieldFormatEnum.text:
Expand Down
4 changes: 0 additions & 4 deletions apps/api/src/domains/admin/feedback/feedback.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,6 @@ describe('FeedbackService Test Suite', () => {
format: FieldFormatEnum.number,
invalidValues: ['not a number', true, {}, [], new Date()],
},
{
format: FieldFormatEnum.boolean,
invalidValues: ['not a boolean', 123, {}, [], new Date()],
},
{
format: FieldFormatEnum.select,
invalidValues: [['option1', 'option2'], 123, true, {}, new Date()],
Expand Down
4 changes: 0 additions & 4 deletions apps/api/src/domains/admin/feedback/feedback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,6 @@ export class FeedbackService {
}

switch (fieldsByKey[fieldKey].format) {
case FieldFormatEnum.boolean:
if (typeof query[fieldKey] !== 'boolean')
throw new BadRequestException(`${fieldKey} must be boolean`);
break;
case FieldFormatEnum.keyword:
if (typeof query[fieldKey] !== 'string')
throw new BadRequestException(`${fieldKey} must be string`);
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/shared/code/code.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export class CodeEntity extends CommonEntity {
@Column('boolean', { default: false })
isVerified: boolean;

@Column('int', { default: 0 })
tryCount: number;

@Column('datetime')
expiredAt: Date;
}
27 changes: 21 additions & 6 deletions apps/api/src/shared/code/code.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('CodeService', () => {
codeEntity.id = faker.number.int();
codeEntity.expiredAt = DateTime.utc().plus({ minutes: 5 }).toJSDate();
});
it('verify code with valid code, key, type', async () => {
it('verifying code succeeds with a valid code, key, type', async () => {
const { code, type } = codeEntity;
jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(codeEntity);

Expand All @@ -156,7 +156,7 @@ describe('CodeService', () => {
Object.assign(codeEntity, { isVerified: true }),
);
});
it('verify code with invalid code', async () => {
it('verifying code fails with an invalid code', async () => {
const { type } = codeEntity;
const invalidCode = faker.string.sample(6);
jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(codeEntity);
Expand All @@ -167,9 +167,24 @@ describe('CodeService', () => {
key,
type,
}),
).rejects.toThrow(BadRequestException);
).rejects.toThrow(new BadRequestException('invalid code'));
});
it('verify code with invalid key', async () => {
it('verifying code fails with an invalid code more than 5 times', async () => {
const { type } = codeEntity;
const invalidCode = faker.string.sample(6);
jest
.spyOn(codeRepo, 'findOneBy')
.mockResolvedValue({ ...codeEntity, tryCount: 5 } as CodeEntity);

await expect(
codeService.verifyCode({
code: invalidCode,
key,
type,
}),
).rejects.toThrow(new BadRequestException('code expired'));
});
it('verifying code fails with an invalid key', async () => {
const { code, type } = codeEntity;
const invalidKey = faker.string.sample(6);
jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(null);
Expand All @@ -182,13 +197,13 @@ describe('CodeService', () => {
}),
).rejects.toThrow(NotFoundException);
});
it('verify code at expired date', async () => {
it('verifying code fails at expired date', async () => {
MockDate.set(new Date(Date.now() + 5 * 60 * 1000 + 1000));
const { code, type } = codeEntity;
jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(codeEntity);

await expect(codeService.verifyCode({ code, key, type })).rejects.toThrow(
BadRequestException,
new BadRequestException('code expired'),
);
MockDate.reset();
});
Expand Down
16 changes: 15 additions & 1 deletion apps/api/src/shared/code/code.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
import crypto from 'crypto';
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';
import { DateTime } from 'luxon';
import { Repository } from 'typeorm';
Expand All @@ -35,6 +37,7 @@ export class CodeService {
constructor(
@InjectRepository(CodeEntity)
private readonly codeRepo: Repository<CodeEntity>,
private readonly eventEmitter: EventEmitter2,
) {}

@Transactional()
Expand Down Expand Up @@ -70,7 +73,12 @@ export class CodeService {
if (codeEntity.isVerified)
throw new BadRequestException('already verified');

if (codeEntity.tryCount >= 5) {
throw new BadRequestException('code expired');
}

if (codeEntity.code !== code) {
this.eventEmitter.emit('code.verify.failed', codeEntity);
throw new BadRequestException('invalid code');
}

Expand Down Expand Up @@ -102,6 +110,12 @@ export class CodeService {
}

private createCode() {
return String(Math.floor(Math.random() * 999998 + 1)).padStart(6, '0');
return String(crypto.randomInt(1, 1000000)).padStart(6, '0');
}

@OnEvent('code.verify.failed', { async: true })
private async handleCodeVerificationFailure(codeEntity: CodeEntity) {
codeEntity.tryCount += 1;
await this.codeRepo.save(codeEntity);
}
}
2 changes: 0 additions & 2 deletions apps/api/src/test-utils/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ export const getRandomValue = (
return faker.string.sample();
case FieldFormatEnum.number:
return faker.number.int();
case FieldFormatEnum.boolean:
return faker.datatype.boolean();
case FieldFormatEnum.select:
return options.length === 0
? undefined
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/test-utils/providers/code.service.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* under the License.
*/

import { EventEmitter2 } from '@nestjs/event-emitter';
import { getRepositoryToken } from '@nestjs/typeorm';

import { mockRepository } from '@/test-utils/util-functions';
Expand All @@ -22,5 +23,6 @@ import { CodeService } from '../../shared/code/code.service';

export const CodeServiceProviders = [
CodeService,
EventEmitter2,
{ provide: getRepositoryToken(CodeEntity), useValue: mockRepository() },
];
2 changes: 1 addition & 1 deletion apps/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"devDependencies": {
"@playwright/test": "^1.40.1",
"axios": "^1.6.3",
"axios": "^1.6.7",
"mysql2": "^3.6.5"
}
}
8 changes: 4 additions & 4 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
"@mui/base": "^5.0.0-beta.23",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-table": "^8.10.7",
"@tanstack/react-table": "^8.11.8",
"@ufb/shared": "^0.1.0",
"@ufb/tailwind": "^0.1.0",
"@ufb/ui": "^0.1.0",
"axios": "^1.6.0",
"axios": "^1.6.7",
"axios-auth-refresh": "^3.3.6",
"cookies-next": "^4.0.0",
"countries-and-timezones": "^3.6.0",
Expand All @@ -63,7 +63,7 @@
"react-i18next": "^13.3.1",
"react-select": "^5.7.7",
"react-use": "^17.4.0",
"recharts": "^2.10.1",
"recharts": "^2.12.0",
"sharp": "^0.32.6",
"tailwind-scrollbar-hide": "^1.1.7",
"zod": "^3.22.4",
Expand All @@ -80,7 +80,7 @@
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.6",
"@types/node": "20.8.7",
"@types/react": "^18.2.30",
"@types/react": "^18.2.55",
"@types/react-beautiful-dnd": "^13.1.6",
"@types/react-datepicker": "^4.19.1",
"@types/react-dom": "^18.2.18",
Expand Down
Loading

0 comments on commit 94cb008

Please sign in to comment.