Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions src/main/java/side/onetime/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import side.onetime.domain.enums.GuideType;
import side.onetime.dto.user.request.*;
import side.onetime.dto.user.response.GetUserPolicyAgreementResponse;
import side.onetime.dto.user.response.GetUserProfileResponse;
import side.onetime.dto.user.response.GetUserSleepTimeResponse;
import side.onetime.dto.user.response.OnboardUserResponse;
import side.onetime.dto.user.response.*;
import side.onetime.global.common.ApiResponse;
import side.onetime.global.common.status.SuccessStatus;
import side.onetime.service.UserService;
Expand Down Expand Up @@ -156,4 +154,53 @@ public ResponseEntity<ApiResponse<SuccessStatus>> logoutUser(
userService.logoutUser(logoutUserRequest);
return ApiResponse.onSuccess(SuccessStatus._LOGOUT_USER);
}

/**
* 유저 가이드 조회 로그 저장 API.
*
* GuideType에 정의된 가이드에 대해 사용자의 조회 로그를 저장합니다.
* 이미 조회한 경우, Conflict 에러를 반환합니다.
*
* @param request 확인 여부를 저장할 가이드 타입 객체
* @return 성공 상태 응답 객체
*/
@PostMapping("/guides/view-log")
public ResponseEntity<ApiResponse<SuccessStatus>> createGuideViewLog(
@Valid @RequestBody CreateGuideViewLogRequest request
) {

userService.createGuideViewLog(request);
return ApiResponse.onSuccess(SuccessStatus._CREATE_GUIDE_VIEW_LOG);
}

/**
* 유저 가이드 조회 로그 조회 API.
*
* GuideType에 정의된 가이드에 대해 사용자의 조회 로그를 조회합니다.
*
* @param guideType 조회할 가이드 타입
* @return 가이드 조회 로그 응답 객체
*/
@GetMapping("/guides/view-log")
public ResponseEntity<ApiResponse<GetGuideViewLogResponse>> getGuideViewLog(
@RequestParam("guide_type") GuideType guideType
){

GetGuideViewLogResponse response = userService.getGuideViewLog(guideType);
return ApiResponse.onSuccess(SuccessStatus._GET_GUIDE_VIEW_LOG, response);
}

/**
* 유저 가이드 조회 로그 삭제 API.
*
* 사용자의 가이드 조회 로그를 삭제합니다.
*
* @return 성공 상태 응답 객체
*/
@DeleteMapping("/guides/view-log")
public ResponseEntity<ApiResponse<SuccessStatus>> deleteGuideViewLog(){

userService.deleteGuideViewLog();
return ApiResponse.onSuccess(SuccessStatus._DELETE_GUIDE_VIEW_LOG);
}
}
45 changes: 45 additions & 0 deletions src/main/java/side/onetime/domain/GuideViewLog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package side.onetime.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import side.onetime.domain.enums.GuideType;

import java.time.LocalDateTime;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(
name = "guide_view_logs",
uniqueConstraints = {
@UniqueConstraint(name = "unique_user_guide_type", columnNames = {"users_id", "guide_type"})
}
)
public class GuideViewLog {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "guide_view_logs_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "users_id", foreignKey = @ForeignKey(name = "guide_view_logs_fk_users_id"), nullable = false)
private User user;

@Enumerated(EnumType.STRING)
@Column(name = "guide_type", nullable = false)
private GuideType guideType;

@Column(name = "viewed_at", nullable = false)
private LocalDateTime viewedAt;

@Builder
public GuideViewLog(User user, GuideType guideType) {
this.user = user;
this.guideType = guideType;
this.viewedAt = LocalDateTime.now();
}
}
7 changes: 7 additions & 0 deletions src/main/java/side/onetime/domain/enums/GuideType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package side.onetime.domain.enums;

public enum GuideType {

SCHEDULE_GUIDE_MODAL_001,
;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package side.onetime.dto.user.request;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.NotNull;
import side.onetime.domain.enums.GuideType;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record CreateGuideViewLogRequest(
@NotNull(message = "가이드 타입은 필수 값입니다.")
GuideType guideType
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package side.onetime.dto.user.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record GetGuideViewLogResponse(
boolean isViewed
) {
public static GetGuideViewLogResponse from(boolean isViewed) {
return new GetGuideViewLogResponse(isViewed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum UserErrorStatus implements BaseErrorCode {
_NOT_FOUND_USER_BY_USERNAME(HttpStatus.UNAUTHORIZED, "USER-002", "username으로 user를 찾을 수 없습니다."),
_NOT_FOUND_USER_BY_USERID(HttpStatus.UNAUTHORIZED, "USER-003", "userId로 user를 찾을 수 없습니다."),
_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "USER-004", "인증된 사용자가 아닙니다."),
_IS_ALREADY_VIEWED_GUIDE(HttpStatus.CONFLICT, "USER-005", "이미 조회한 가이드입니다."),
_NOT_FOUND_GUIDE(HttpStatus.NOT_FOUND, "USER-006", "가이드를 찾을 수 없습니다."),
;

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public enum SuccessStatus implements BaseCode {
_GET_USER_SLEEP_TIME(HttpStatus.OK, "200", "유저 수면 시간 조회에 성공했습니다."),
_UPDATE_USER_SLEEP_TIME(HttpStatus.OK, "200", "유저 수면 시간 수정에 성공했습니다."),
_LOGOUT_USER(HttpStatus.OK, "200", "유저 로그아웃에 성공했습니다."),
_CREATE_GUIDE_VIEW_LOG(HttpStatus.CREATED, "201", "유저 가이드 조회 로그 저장에 성공했습니다."),
_GET_GUIDE_VIEW_LOG(HttpStatus.OK, "200", "유저 가이드 조회 로그 조회에 성공했습니다."),
_DELETE_GUIDE_VIEW_LOG(HttpStatus.OK, "200", "유저 가이드 조회 로그 삭제에 성공했습니다."),
// Fixed
_GET_USER_FIXED_SCHEDULE(HttpStatus.OK, "200", "유저 고정 스케줄 조회에 성공했습니다."),
_UPDATE_USER_FIXED_SCHEDULE(HttpStatus.OK, "200", "유저 고정 스케줄 수정에 성공했습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package side.onetime.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import side.onetime.domain.GuideViewLog;
import side.onetime.domain.User;
import side.onetime.domain.enums.GuideType;

import java.util.Optional;

public interface GuideViewLogRepository extends JpaRepository<GuideViewLog, Long> {

boolean existsByUserAndGuideType(User user, GuideType guideType);

Optional<GuideViewLog> findByUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static side.onetime.domain.QEvent.event;
import static side.onetime.domain.QEventParticipation.eventParticipation;
import static side.onetime.domain.QFixedSelection.fixedSelection;
import static side.onetime.domain.QGuideViewLog.guideViewLog;
import static side.onetime.domain.QMember.member;
import static side.onetime.domain.QSchedule.schedule;
import static side.onetime.domain.QSelection.selection;
Expand Down Expand Up @@ -96,6 +97,10 @@ public void withdraw(User activeUser) {
.where(eventParticipation.user.eq(activeUser))
.execute();

queryFactory.delete(guideViewLog)
.where(guideViewLog.user.eq(activeUser))
.execute();

queryFactory.update(user)
.set(user.providerId, Expressions.nullExpression())
.set(user.status, Status.DELETED)
Expand Down
69 changes: 65 additions & 4 deletions src/main/java/side/onetime/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import side.onetime.domain.GuideViewLog;
import side.onetime.domain.RefreshToken;
import side.onetime.domain.User;
import side.onetime.domain.enums.GuideType;
import side.onetime.dto.user.request.*;
import side.onetime.dto.user.response.GetUserPolicyAgreementResponse;
import side.onetime.dto.user.response.GetUserProfileResponse;
import side.onetime.dto.user.response.GetUserSleepTimeResponse;
import side.onetime.dto.user.response.OnboardUserResponse;
import side.onetime.dto.user.response.*;
import side.onetime.exception.CustomException;
import side.onetime.exception.status.UserErrorStatus;
import side.onetime.repository.GuideViewLogRepository;
import side.onetime.repository.RefreshTokenRepository;
import side.onetime.repository.UserRepository;
import side.onetime.util.JwtUtil;
Expand All @@ -26,6 +26,7 @@ public class UserService {
private final RefreshTokenRepository refreshTokenRepository;
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final GuideViewLogRepository guideViewLogRepository;

/**
* 유저 온보딩 처리 메서드.
Expand Down Expand Up @@ -201,4 +202,64 @@ public void logoutUser(LogoutUserRequest request) {
String browserId = jwtUtil.getClaimFromToken(refreshToken, "browserId", String.class);
refreshTokenRepository.deleteRefreshToken(userId, browserId);
}

/**
* 유저 가이드 조회 로그 저장 메서드.
*
* GuideType에 정의된 가이드에 대해 사용자의 조회 로그를 저장합니다.
* 이미 조회한 경우, Conflict 에러를 반환합니다.
*
* @param request 확인 여부를 저장할 가이드 타입 객체
*/
@Transactional
public void createGuideViewLog(CreateGuideViewLogRequest request) {
User user = userRepository.findById(UserAuthorizationUtil.getLoginUserId())
.orElseThrow(() -> new CustomException(UserErrorStatus._NOT_FOUND_USER));
GuideType guideType = request.guideType();

boolean isViewed = guideViewLogRepository.existsByUserAndGuideType(user, guideType);
if (isViewed) {
throw new CustomException(UserErrorStatus._IS_ALREADY_VIEWED_GUIDE);
}

GuideViewLog guideViewLog = GuideViewLog.builder()
.user(user)
.guideType(guideType)
.build();

guideViewLogRepository.save(guideViewLog);
}

/**
* 유저 가이드 조회 로그 조회 메서드.
*
* GuideType에 정의된 가이드에 대해 사용자의 조회 로그를 조회합니다.
*
* @param guideType 조회할 가이드 타입
* @return 가이드 조회 로그 응답 데이터
*/
@Transactional(readOnly = true)
public GetGuideViewLogResponse getGuideViewLog(GuideType guideType) {
User user = userRepository.findById(UserAuthorizationUtil.getLoginUserId())
.orElseThrow(() -> new CustomException(UserErrorStatus._NOT_FOUND_USER));

boolean isViewed = guideViewLogRepository.existsByUserAndGuideType(user, guideType);
return GetGuideViewLogResponse.from(isViewed);
}

/**
* 유저 가이드 조회 로그 삭제 메서드.
*
* 사용자의 가이드 조회 로그를 삭제합니다.
*/
@Transactional
public void deleteGuideViewLog() {
User user = userRepository.findById(UserAuthorizationUtil.getLoginUserId())
.orElseThrow(() -> new CustomException(UserErrorStatus._NOT_FOUND_USER));

GuideViewLog guideViewLog = guideViewLogRepository.findByUser(user)
.orElseThrow(() -> new CustomException(UserErrorStatus._NOT_FOUND_GUIDE));

guideViewLogRepository.delete(guideViewLog);
}
}
Loading
Loading