-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TNT-102] feat: 트레이너 초대 코드 표시 및 재발급 API 구현 (#16)
* [TNT-102] feat: 트레이너, 트레이니 Entity 구현 * [TNT-102] feat: 에러 메시지 추가 * [TNT-102] AuthMember 어노테이션 추가 * [TNT-102] feat: 초대 코드 표시 및 재발급 API 구현 * [TNT-102] chore: test 관련 설정 수정 및 추가 * [TNT-102] feat: Exception 에러 스택 추가할 수 있도록 수정 * [TNT-102] feat: 초대 코드 발급 기능 구현 * [TNT-102] fix: deleted null 찾도록 수정 * [TNT-102] test: 단위 테스트 작성 * [TNT-102] test: 통합 테스트 작성 * [TNT-102] refactor: 컨벤션에 따라 선언 위치 수정 * [TNT-102] test: 초대 코드 재발급 단위 테스트 추가 * [TNT-102] chore: Exception 파일 sonar exclude 추가
- Loading branch information
Showing
18 changed files
with
492 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/main/java/com/tnt/application/trainer/TrainerService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.tnt.application.trainer; | ||
|
||
import static com.tnt.global.error.model.ErrorMessage.*; | ||
|
||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import com.tnt.domain.trainer.Trainer; | ||
import com.tnt.dto.trainer.response.InvitationCodeResponse; | ||
import com.tnt.global.error.exception.NotFoundException; | ||
import com.tnt.infrastructure.mysql.repository.trainer.TrainerRepository; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
public class TrainerService { | ||
|
||
private final TrainerRepository trainerRepository; | ||
|
||
public InvitationCodeResponse getInvitationCode(String memberId) { | ||
Trainer trainer = getTrainer(memberId); | ||
|
||
return new InvitationCodeResponse(String.valueOf(trainer.getId()), trainer.getInvitationCode()); | ||
} | ||
|
||
@Transactional | ||
public InvitationCodeResponse reissueInvitationCode(String memberId) { | ||
Trainer trainer = getTrainer(memberId); | ||
trainer.setNewInvitationCode(); | ||
|
||
return new InvitationCodeResponse(String.valueOf(trainer.getId()), trainer.getInvitationCode()); | ||
} | ||
|
||
public Trainer getTrainer(String memberId) { | ||
return trainerRepository.findByMemberIdAndDeletedAtIsNull(Long.valueOf(memberId)) | ||
.orElseThrow(() -> new NotFoundException(TRAINER_NOT_FOUND)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.tnt.domain.pt; | ||
|
||
import java.time.LocalDate; | ||
|
||
import com.tnt.global.common.entity.BaseTimeEntity; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@Table(name = "pt_trainer_trainee") | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class PtTrainerTrainee extends BaseTimeEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(name = "id", nullable = false, unique = true) | ||
private Long id; | ||
|
||
@Column(name = "trainer_id", nullable = false) | ||
private Long trainerId; | ||
|
||
@Column(name = "trainee_id", nullable = false) | ||
private Long traineeId; | ||
|
||
@Column(name = "started_at", nullable = false) | ||
private LocalDate startedAt; | ||
|
||
@Column(name = "finished_pt_count", nullable = false) | ||
private int finishedPtCount; | ||
|
||
@Column(name = "total_pt_count", nullable = false) | ||
private int totalPtCount; | ||
|
||
@Column(name = "deleted_at") | ||
private LocalDate deletedAt; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package com.tnt.domain.trainer; | ||
|
||
import static com.tnt.global.error.model.ErrorMessage.*; | ||
import static io.micrometer.common.util.StringUtils.*; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.security.MessageDigest; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.time.LocalDateTime; | ||
import java.util.Objects; | ||
import java.util.UUID; | ||
|
||
import com.tnt.global.common.entity.BaseTimeEntity; | ||
import com.tnt.global.error.exception.TnTException; | ||
|
||
import io.hypersistence.utils.hibernate.id.Tsid; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@Getter | ||
@Table(name = "trainer") | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class Trainer extends BaseTimeEntity { | ||
|
||
public static final int INVITATION_CODE_LENGTH = 8; | ||
|
||
@Id | ||
@Tsid | ||
@Column(name = "id", nullable = false, unique = true) | ||
private Long id; | ||
|
||
@Column(name = "member_id", nullable = false) | ||
private Long memberId; | ||
|
||
@Column(name = "invitation_code", nullable = false, length = INVITATION_CODE_LENGTH) | ||
private String invitationCode; | ||
|
||
@Column(name = "deleted_at") | ||
private LocalDateTime deletedAt; | ||
|
||
@Builder | ||
public Trainer(Long id, Long memberId) { | ||
this.id = id; | ||
this.memberId = Objects.requireNonNull(memberId, TRAINER_NULL_MEMBER_ID.getMessage()); | ||
setNewInvitationCode(); | ||
} | ||
|
||
public void setNewInvitationCode() { | ||
byte[] hashBytes; | ||
StringBuilder sb = new StringBuilder(); | ||
|
||
String uuidString = UUID.randomUUID().toString(); | ||
byte[] uuidStringBytes = uuidString.getBytes(StandardCharsets.UTF_8); | ||
|
||
try { | ||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); | ||
hashBytes = messageDigest.digest(uuidStringBytes); | ||
} catch (NoSuchAlgorithmException e) { | ||
throw new TnTException(TRAINER_INVITATION_CODE_GENERATE_FAILED, e); | ||
} | ||
|
||
for (int j = 0; j < 4; j++) { | ||
sb.append(String.format("%02x", hashBytes[j])); | ||
} | ||
|
||
this.invitationCode = validateInvitationCode(sb.toString().toUpperCase()); | ||
} | ||
|
||
private String validateInvitationCode(String invitationCode) { | ||
if (isBlank(invitationCode) || invitationCode.length() != INVITATION_CODE_LENGTH) { | ||
throw new IllegalArgumentException(TRAINER_INVALID_INVITATION_CODE.getMessage()); | ||
} | ||
|
||
return invitationCode; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/tnt/dto/trainer/response/InvitationCodeResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.tnt.dto.trainer.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@Schema(description = "트레이너의 초대 코드 응답") | ||
public record InvitationCodeResponse( | ||
|
||
@Schema(description = "트레이너 id", example = "23984725", type = "string") | ||
String trainerId, | ||
|
||
@Schema(description = "트레이너의 초대 코드", example = "2H9DG4X3", type = "string") | ||
String invitationCode | ||
) { | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/tnt/global/auth/annotation/AuthMember.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.tnt.global.auth.annotation; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
|
||
import io.swagger.v3.oas.annotations.Hidden; | ||
|
||
@Hidden | ||
@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : username") | ||
public @interface AuthMember { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
src/main/java/com/tnt/infrastructure/mysql/repository/trainer/TrainerRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.tnt.infrastructure.mysql.repository.trainer; | ||
|
||
import java.util.Optional; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import com.tnt.domain.trainer.Trainer; | ||
|
||
public interface TrainerRepository extends JpaRepository<Trainer, Long> { | ||
|
||
Optional<Trainer> findByMemberIdAndDeletedAtIsNull(Long memberId); | ||
} |
40 changes: 40 additions & 0 deletions
40
src/main/java/com/tnt/presentation/trainer/TrainerController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.tnt.presentation.trainer; | ||
|
||
import static org.springframework.http.HttpStatus.*; | ||
|
||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PutMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import com.tnt.application.trainer.TrainerService; | ||
import com.tnt.dto.trainer.response.InvitationCodeResponse; | ||
import com.tnt.global.auth.annotation.AuthMember; | ||
|
||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Tag(name = "트레이너", description = "트레이너 관련 API") | ||
@RestController | ||
@RequestMapping("/trainers") | ||
@RequiredArgsConstructor | ||
public class TrainerController { | ||
|
||
private final TrainerService trainerService; | ||
|
||
@Operation(summary = "트레이너 초대 코드 불러오기 API") | ||
@ResponseStatus(OK) | ||
@GetMapping("/invitation-code") | ||
public InvitationCodeResponse getInvitationCode(@AuthMember String memberId) { | ||
return trainerService.getInvitationCode(memberId); | ||
} | ||
|
||
@Operation(summary = "트레이너 초대 코드 재발급 API") | ||
@ResponseStatus(CREATED) | ||
@PutMapping("/invitation-code") | ||
public InvitationCodeResponse reissueInvitationCode(@AuthMember String memberId) { | ||
return trainerService.reissueInvitationCode(memberId); | ||
} | ||
} |
Oops, something went wrong.