Skip to content

Commit

Permalink
Merge pull request #46 from DevKor-github/feature/user
Browse files Browse the repository at this point in the history
Feature/user
  • Loading branch information
KimSeongHyeonn authored Jul 19, 2024
2 parents 869120a + 8976250 commit 435359c
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 19 deletions.
33 changes: 33 additions & 0 deletions src/entities/point-history.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { CommonEntity } from './common.entity';
import { UserEntity } from './user.entity';

@Entity('point_history')
export class PointHistoryEntity extends CommonEntity {
@PrimaryGeneratedColumn({ type: 'bigint' })
id: number;

@Column({ nullable: false })
userId: number;

@Column('varchar', { nullable: false })
history: string;

@Column('int', { nullable: false })
changePoint: number;

@Column('int', { nullable: false })
resultPoint: number;

@JoinColumn({ name: 'userId' })
@ManyToOne(() => UserEntity, (userEntity) => userEntity.pointHistories, {
onDelete: 'CASCADE',
})
user: UserEntity;
}
8 changes: 8 additions & 0 deletions src/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { CommentEntity } from './comment.entity';
import { CourseReviewEntity } from './course-review.entity';
import { CourseReviewRecommendEntity } from './course-review-recommend.entity';
import { ClubLikeEntity } from './club-like.entity';
import { PointHistoryEntity } from './point-history.entity';

@Entity('user')
export class UserEntity extends CommonEntity {
Expand Down Expand Up @@ -99,4 +100,11 @@ export class UserEntity extends CommonEntity {

@OneToMany(() => ClubLikeEntity, (clubLike) => clubLike.user)
clubLikes: ClubLikeEntity[];

@OneToMany(
() => PointHistoryEntity,
(pointHistoryEntity) => pointHistoryEntity.user,
{ cascade: true },
)
pointHistories: PointHistoryEntity[];
}
2 changes: 1 addition & 1 deletion src/user/dto/check-possible-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export class checkPossibleResponseDto {
this.possible = possible;
}

@ApiProperty({ description: '학번 사용 가능 여부' })
@ApiProperty({ description: '사용 가능 여부' })
possible: boolean;
}
23 changes: 23 additions & 0 deletions src/user/dto/get-point-history.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { PointHistoryEntity } from 'src/entities/point-history.entity';

export class GetPointHistoryResponseDto {
constructor(pointHistoryEntity: PointHistoryEntity) {
this.date = pointHistoryEntity.createdAt;
this.history = pointHistoryEntity.history;
this.changePoint = pointHistoryEntity.changePoint;
this.resultPoint = pointHistoryEntity.resultPoint;
}

@ApiProperty({ description: '일시' })
date: Date;

@ApiProperty({ description: '내역' })
history: string;

@ApiProperty({ description: '변경 포인트' })
changePoint: number;

@ApiProperty({ description: '결과 포인트' })
resultPoint: number;
}
8 changes: 5 additions & 3 deletions src/user/dto/set-profile-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsDate, IsOptional, IsString } from 'class-validator';
import { IsDate, IsNotEmpty, IsOptional, IsString } from 'class-validator';

export class SetProfileRequestDto {
@IsOptional()
Expand All @@ -21,13 +21,15 @@ export class SetProfileRequestDto {
@IsString()
@ApiProperty({ description: '전공' })
major: string;
}

@IsOptional()
export class SetExchangeDayReqeustDto {
@IsNotEmpty()
@IsDate()
@ApiProperty({ description: '교환학생 시작 날짜', example: 'yyyy-mm-dd' })
startDay: Date;

@IsOptional()
@IsNotEmpty()
@IsDate()
@ApiProperty({ description: '교환학생 끝 날짜', example: 'yyyy-mm-dd' })
endDay: Date;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';

export class SetProfileResponseDto {
export class SetResponseDto {
constructor(set: boolean) {
this.set = set;
}

@ApiProperty({ description: '프로필 설정 성공 여부' })
@ApiProperty({ description: '설정 성공 여부' })
set: boolean;
}
51 changes: 47 additions & 4 deletions src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { UserService } from './user.service';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
import { User } from 'src/decorators/user.decorator';
import { AuthorizedUserDto } from 'src/auth/dto/authorized-user-dto';
import { SetProfileResponseDto } from './dto/set-profile-response.dto';
import { SetProfileRequestDto } from './dto/set-profile-request.dto';
import { SetResponseDto } from './dto/set-response.dto';
import {
SetExchangeDayReqeustDto,
SetProfileRequestDto,
} from './dto/set-profile-request.dto';
import { GetProfileResponseDto } from './dto/get-profile-response.dto';
import {
ApiBearerAuth,
Expand All @@ -13,6 +16,7 @@ import {
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { GetPointHistoryResponseDto } from './dto/get-point-history.dto';

@ApiTags('User')
@ApiBearerAuth('accessToken')
Expand All @@ -31,17 +35,39 @@ export class UserController {
@ApiResponse({
status: 200,
description: '프로필 설정 성공',
type: SetProfileResponseDto,
type: SetResponseDto,
})
@Patch('/profile')
async setProfile(
@Body() profileDto: SetProfileRequestDto,
@User() user: AuthorizedUserDto,
): Promise<SetProfileResponseDto> {
): Promise<SetResponseDto> {
const id = user.id;
return await this.userService.setProfile(id, profileDto);
}

@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: '교환 남은 일자 설정',
description: '교환학생 남은 일자를 설정(변경) 합니다',
})
@ApiBody({
type: SetExchangeDayReqeustDto,
})
@ApiResponse({
status: 200,
description: '교환 남은 일자 설정 성공',
type: SetResponseDto,
})
@Patch('/exchange-day')
async setExchangeDay(
@Body() requestDto: SetExchangeDayReqeustDto,
@User() user: AuthorizedUserDto,
): Promise<SetResponseDto> {
const id = user.id;
return await this.userService.setExchangeDay(id, requestDto);
}

@ApiOperation({
summary: '프로필 조회',
description: '프로필을 조회 합니다',
Expand All @@ -59,4 +85,21 @@ export class UserController {
const id = user.id;
return await this.userService.getProfile(id);
}

@ApiOperation({
summary: '포인트 내역 조회',
description: '포인트 획득/사용 내역을 조회 합니다',
})
@ApiResponse({
status: 200,
description: '포인트 내역 조회 성공',
type: [GetPointHistoryResponseDto],
})
@UseGuards(JwtAuthGuard)
@Get('point-history')
async getPointHistory(
@User() user: AuthorizedUserDto,
): Promise<GetPointHistoryResponseDto[]> {
return await this.userService.getPointHistory(user);
}
}
3 changes: 2 additions & 1 deletion src/user/user.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { UserService } from './user.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserEntity } from 'src/entities/user.entity';
import { UserRepository } from './user.repository';
import { PointHistoryEntity } from 'src/entities/point-history.entity';

@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
imports: [TypeOrmModule.forFeature([UserEntity, PointHistoryEntity])],
controllers: [UserController],
providers: [UserService, UserRepository],
exports: [UserService],
Expand Down
14 changes: 13 additions & 1 deletion src/user/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { UserEntity } from 'src/entities/user.entity';
import { DataSource, Repository } from 'typeorm';
import { CreateUserRequestDto } from './dto/create-user-request.dto';
import { Injectable } from '@nestjs/common';
import { SetProfileRequestDto } from './dto/set-profile-request.dto';
import {
SetExchangeDayReqeustDto,
SetProfileRequestDto,
} from './dto/set-profile-request.dto';

@Injectable()
export class UserRepository extends Repository<UserEntity> {
Expand Down Expand Up @@ -67,6 +70,15 @@ export class UserRepository extends Repository<UserEntity> {
return updateResult.affected ? true : false;
}

async setExchangeDay(
id: number,
requestDto: SetExchangeDayReqeustDto,
): Promise<boolean> {
const updateResult = await this.update({ id: id }, requestDto);

return updateResult.affected ? true : false;
}

async verifyUser(id: number, verify: boolean): Promise<boolean> {
const updateResult = await this.update(
{ id: id },
Expand Down
90 changes: 83 additions & 7 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import {
BadRequestException,
Injectable,
NotImplementedException,
InternalServerErrorException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from './user.repository';
import { CreateUserRequestDto } from './dto/create-user-request.dto';
import { hash, compare } from 'bcrypt';
import * as argon2 from 'argon2';
import { checkPossibleResponseDto } from './dto/check-possible-response.dto';
import { SetProfileResponseDto } from './dto/set-profile-response.dto';
import { SetResponseDto } from './dto/set-response.dto';
import { GetProfileResponseDto } from './dto/get-profile-response.dto';
import { SetProfileRequestDto } from './dto/set-profile-request.dto';
import {
SetExchangeDayReqeustDto,
SetProfileRequestDto,
} from './dto/set-profile-request.dto';
import { UserEntity } from 'src/entities/user.entity';
import { DataSource, Repository } from 'typeorm';
import { PointHistoryEntity } from 'src/entities/point-history.entity';
import { AuthorizedUserDto } from 'src/auth/dto/authorized-user-dto';
import { GetPointHistoryResponseDto } from './dto/get-point-history.dto';

@Injectable()
export class UserService {
constructor(
@InjectRepository(UserRepository)
private readonly userRepository: UserRepository,
private readonly dataSource: DataSource,
@InjectRepository(PointHistoryEntity)
private readonly pointHistoryRepository: Repository<PointHistoryEntity>,
) {}

async createUser(createUserDto: CreateUserRequestDto): Promise<UserEntity> {
Expand Down Expand Up @@ -47,7 +57,7 @@ export class UserService {
async deleteUser(userId: number): Promise<void> {
const isDeleted = await this.userRepository.deleteUser(userId);
if (!isDeleted) {
throw new NotImplementedException('remove user failed!');
throw new InternalServerErrorException('remove user failed!');
}
}

Expand All @@ -74,13 +84,28 @@ export class UserService {
async setProfile(
id: number,
profileDto: SetProfileRequestDto,
): Promise<SetProfileResponseDto> {
): Promise<SetResponseDto> {
const isSet = await this.userRepository.setProfile(id, profileDto);
if (!isSet) {
throw new NotImplementedException('Profile setting failed!');
throw new InternalServerErrorException('Profile setting failed!');
}

return new SetProfileResponseDto(true);
return new SetResponseDto(true);
}

async setExchangeDay(
id: number,
requestDto: SetExchangeDayReqeustDto,
): Promise<SetResponseDto> {
if (requestDto.startDay >= requestDto.endDay) {
throw new BadRequestException('StartDay should be earlier than EndDay!');
}
const isSet = await this.userRepository.setExchangeDay(id, requestDto);
if (!isSet) {
throw new InternalServerErrorException('Exchange Day setting failed!');
}

return new SetResponseDto(true);
}

async getProfile(id: number): Promise<GetProfileResponseDto> {
Expand Down Expand Up @@ -139,4 +164,55 @@ export class UserService {
const hashedPassword = await hash(newPassword, 10);
return await this.userRepository.updatePassword(userId, hashedPassword);
}

async changePoint(
userId: number,
changePoint: number,
history: string,
): Promise<number> {
const user = await this.userRepository.findUserById(userId);
if (!user) {
throw new BadRequestException('Wrong userId!');
}
const originPoint = user.point;
if (originPoint + changePoint < 0) {
throw new BadRequestException("Don't have enough point!");
}

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
user.point = originPoint + changePoint;
await queryRunner.manager.save(user);

const newHistory = queryRunner.manager.create(PointHistoryEntity, {
userId: userId,
history: history,
changePoint: changePoint,
resultPoint: user.point,
});
await queryRunner.manager.save(newHistory);

await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}

return user.point;
}

async getPointHistory(
user: AuthorizedUserDto,
): Promise<GetPointHistoryResponseDto[]> {
const histories = await this.pointHistoryRepository.find({
where: { userId: user.id },
order: { createdAt: 'DESC' },
});

return histories.map((history) => new GetPointHistoryResponseDto(history));
}
}

0 comments on commit 435359c

Please sign in to comment.