Skip to content

Commit

Permalink
Merge pull request #191 from line/dev
Browse files Browse the repository at this point in the history
release: 4.2408.39
  • Loading branch information
h4l-yup authored Feb 20, 2024
2 parents 94cb008 + 4212a05 commit 6e68f46
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 186 deletions.
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"nestjs-cls": "^3.6.0",
"nestjs-pino": "^3.5.0",
"nestjs-typeorm-paginate": "^4.0.4",
"nodemailer": "^6.9.7",
"nodemailer": "^6.9.9",
"passport": "^0.7.0",
"passport-custom": "^1.1.1",
"passport-jwt": "^4.0.1",
Expand Down
15 changes: 7 additions & 8 deletions apps/api/src/domains/admin/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,11 @@ import { UserService } from '../user/user.service';
import type {
JwtDto,
SendEmailCodeDto,
SignUpInvitationUserDto,
ValidateEmailUserDto,
VerifyEmailCodeDto,
} from './dtos';
import {
SignUpEmailUserDto,
SignUpInvitationUserDto,
SignUpOauthUserDto,
} from './dtos';
import { SignUpEmailUserDto, SignUpOauthUserDto } from './dtos';
import { PasswordNotMatchException, UserBlockedException } from './exceptions';

@Injectable()
Expand Down Expand Up @@ -96,11 +93,13 @@ export class AuthService {
}

async verifyEmailCode({ code, email }: VerifyEmailCodeDto) {
await this.codeService.verifyCode({
const { error } = await this.codeService.verifyCode({
type: CodeTypeEnum.EMAIL_VEIRIFICATION,
key: email,
code,
});

if (error) throw error;
}

async validateEmailUser({ email, password }: ValidateEmailUserDto) {
Expand Down Expand Up @@ -131,15 +130,15 @@ export class AuthService {
return await this.createUserService.createEmailUser(dto);
}

@Transactional()
async signUpInvitationUser(dto: SignUpInvitationUserDto) {
const { code, ...rest } = dto;

await this.codeService.verifyCode({
const { error } = await this.codeService.verifyCode({
type: CodeTypeEnum.USER_INVITATION,
key: dto.email,
code,
});
if (error) throw error;

const data = await this.codeService.getDataByCodeAndType(
CodeTypeEnum.USER_INVITATION,
Expand Down
8 changes: 5 additions & 3 deletions apps/api/src/domains/admin/user/user-password.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ 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 type { ResetPasswordDto } from './dtos';
import { ChangePasswordDto } from './dtos';
import { UserEntity } from './entities/user.entity';
import { InvalidPasswordException, UserNotFoundException } from './exceptions';

Expand All @@ -48,17 +49,18 @@ export class UserPasswordService {
await this.resetPasswordMailingService.send({ email, code });
}

@Transactional()
async resetPassword({ email, code, password }: ResetPasswordDto) {
const user = await this.userRepo.findOneBy({ email });
if (!user) throw new UserNotFoundException();

await this.codeService.verifyCode({
const { error } = await this.codeService.verifyCode({
type: CodeTypeEnum.RESET_PASSWORD,
key: email,
code,
});

if (error) throw error;

return await this.userRepo.save(
Object.assign(user, {
hashPassword: await this.createHashPassword(password),
Expand Down
44 changes: 20 additions & 24 deletions apps/api/src/shared/code/code.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,12 @@ describe('CodeService', () => {
const invalidCode = faker.string.sample(6);
jest.spyOn(codeRepo, 'findOneBy').mockResolvedValue(codeEntity);

await expect(
codeService.verifyCode({
code: invalidCode,
key,
type,
}),
).rejects.toThrow(new BadRequestException('invalid code'));
const { error } = await codeService.verifyCode({
code: invalidCode,
key,
type,
});
expect(error).toEqual(new BadRequestException('invalid code'));
});
it('verifying code fails with an invalid code more than 5 times', async () => {
const { type } = codeEntity;
Expand All @@ -176,35 +175,32 @@ describe('CodeService', () => {
.spyOn(codeRepo, 'findOneBy')
.mockResolvedValue({ ...codeEntity, tryCount: 5 } as CodeEntity);

await expect(
codeService.verifyCode({
code: invalidCode,
key,
type,
}),
).rejects.toThrow(new BadRequestException('code expired'));
const { error } = await codeService.verifyCode({
code: invalidCode,
key,
type,
});
expect(error).toEqual(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);

await expect(
codeService.verifyCode({
code,
key: invalidKey,
type,
}),
).rejects.toThrow(NotFoundException);
const { error } = await codeService.verifyCode({
code,
key: invalidKey,
type,
});
expect(error).toEqual(new NotFoundException('not found code'));
});
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(
new BadRequestException('code expired'),
);
const { error } = await codeService.verifyCode({ code, key, type });
expect(error).toEqual(new BadRequestException('code expired'));
MockDate.reset();
});
});
Expand Down
23 changes: 9 additions & 14 deletions apps/api/src/shared/code/code.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
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 @@ -37,7 +36,6 @@ export class CodeService {
constructor(
@InjectRepository(CodeEntity)
private readonly codeRepo: Repository<CodeEntity>,
private readonly eventEmitter: EventEmitter2,
) {}

@Transactional()
Expand All @@ -59,6 +57,7 @@ export class CodeService {
.plus({ seconds: durationSec ?? SECONDS })
.toJSDate(),
data: type === CodeTypeEnum.USER_INVITATION ? dto.data : undefined,
tryCount: 0,
}),
);

Expand All @@ -69,24 +68,26 @@ export class CodeService {
async verifyCode({ code, key, type }: VerifyCodeDto) {
const codeEntity = await this.codeRepo.findOneBy({ key, type });

if (!codeEntity) throw new NotFoundException('not found code');
if (!codeEntity) return { error: new NotFoundException('not found code') };
if (codeEntity.isVerified)
throw new BadRequestException('already verified');
return { error: new BadRequestException('already verified') };

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

if (codeEntity.code !== code) {
this.eventEmitter.emit('code.verify.failed', codeEntity);
throw new BadRequestException('invalid code');
codeEntity.tryCount += 1;
await this.codeRepo.save(codeEntity);
return { error: new BadRequestException('invalid code') };
}

if (DateTime.utc() > DateTime.fromJSDate(codeEntity.expiredAt)) {
throw new BadRequestException('code expired');
return { error: new BadRequestException('code expired') };
}

await this.codeRepo.save(Object.assign(codeEntity, { isVerified: true }));
return { error: null };
}

async getDataByCodeAndType(
Expand All @@ -112,10 +113,4 @@ export class CodeService {
private createCode() {
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/providers/code.service.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* under the License.
*/

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

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

export const CodeServiceProviders = [
CodeService,
EventEmitter2,
{ provide: getRepositoryToken(CodeEntity), useValue: mockRepository() },
];
10 changes: 5 additions & 5 deletions apps/web/src/components/etc/DateRangePicker/DateRangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,22 @@ const DateRangePicker: React.FC<IProps> = (props) => {
},
{
label: t('text.date.yesterday'),
startDate: dayjs().subtract(1, 'days').startOf('day').toDate(),
endDate: dayjs().endOf('day').toDate(),
startDate: dayjs().subtract(1, 'day').startOf('day').toDate(),
endDate: dayjs().subtract(1, 'day').endOf('day').toDate(),
},
{
label: t('text.date.before-days', { day: 7 }),
startDate: dayjs().subtract(7, 'days').startOf('day').toDate(),
startDate: dayjs().subtract(7, 'day').startOf('day').toDate(),
endDate: dayjs().endOf('day').toDate(),
},
{
label: t('text.date.before-days', { day: 30 }),
startDate: dayjs().subtract(30, 'days').startOf('day').toDate(),
startDate: dayjs().subtract(30, 'day').startOf('day').toDate(),
endDate: dayjs().endOf('day').toDate(),
},
{
label: t('text.date.before-days', { day: 90 }),
startDate: dayjs().subtract(90, 'days').startOf('day').toDate(),
startDate: dayjs().subtract(90, 'day').startOf('day').toDate(),
endDate: dayjs().endOf('day').toDate(),
},
];
Expand Down
45 changes: 20 additions & 25 deletions apps/web/src/containers/tables/FeedbackTable/FeedbackTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ const FeedbackTable: React.FC<IFeedbackTableProps> = (props) => {
setRowSelection({});
}, [limit, query]);

const { data: channelData, refetch: refetchChannelData } = useOAIQuery({
path: '/api/admin/projects/{projectId}/channels/{channelId}',
variables: { channelId, projectId },
});

const fieldData = channelData?.fields ?? [];

const formattedQuery = useMemo(
() =>
produce(query, (draft) => {
Expand All @@ -101,37 +108,25 @@ const FeedbackTable: React.FC<IFeedbackTableProps> = (props) => {
draft['issueIds'] = [draft['issueIds']].map((v) => +v);
}
Object.entries(draft).forEach(([key, value]) => {
if (typeof value === 'string' && value.split('~').length === 2) {
const [gte, lt] = value.split('~');
draft[key] = {
gte: dayjs(gte).startOf('day').toISOString(),
lt: dayjs(lt).endOf('day').toISOString(),
};
}
if (value === 'true' || value === 'false') {
draft[key] = value === 'true';
const field = fieldData.find((v) => v.key === key);
if (field) {
if (field.format === 'date') {
const [gte, lt] = value.split('~');
draft[key] = {
gte: dayjs(gte).startOf('day').toISOString(),
lt: dayjs(lt).endOf('day').toISOString(),
};
}
if (field.format === 'number') {
draft[key] = Number(value);
}
}
});
if (createdAtRange) {
draft['createdAt'] = {
gte: dayjs(createdAtRange.startDate).startOf('day').toISOString(),
lt: dayjs(createdAtRange.endDate).endOf('day').toISOString(),
};
} else {
delete draft['createdAt'];
}
}
}),
[issueId, query, createdAtRange, sub],
[issueId, query, createdAtRange, sub, fieldData],
);

const { data: channelData, refetch: refetchChannelData } = useOAIQuery({
path: '/api/admin/projects/{projectId}/channels/{channelId}',
variables: { channelId, projectId },
});

const fieldData = channelData?.fields ?? [];

const {
data,
refetch: refetchFeedbackData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ const FeedbackTableBar: React.FC<IProps> = (props) => {
{fieldData && (
<TableSearchInput
searchItems={searchItems}
onChangeQuery={setQuery}
onChangeQuery={(input) => {
const { createdAt } = query;
setQuery({ createdAt, ...input });
}}
query={query}
/>
)}
Expand Down
Loading

0 comments on commit 6e68f46

Please sign in to comment.