-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #62 from Project-Catcher/feat-dj-admin-user
어드민 유저 API
- Loading branch information
Showing
24 changed files
with
971 additions
and
0 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
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
8 changes: 8 additions & 0 deletions
8
src/main/java/com/catcher/core/domain/GeneralSearchFilterType.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,8 @@ | ||
package com.catcher.core.domain; | ||
|
||
public interface GeneralSearchFilterType { | ||
|
||
String getMatchedField(); | ||
|
||
GeneralSearchFilterType getDefaultField(); | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/catcher/core/domain/UserSearchFilterType.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,16 @@ | ||
package com.catcher.core.domain; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum UserSearchFilterType implements GeneralSearchFilterType { | ||
ID("username"), NICKNAME("nickname"), EMAIL("email"), PHONE_NUMBER("phone"), NONE(""); | ||
|
||
private final String matchedField; | ||
|
||
public GeneralSearchFilterType getDefaultField() { | ||
return NONE; | ||
} | ||
} |
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,6 @@ | ||
package com.catcher.core.domain; | ||
|
||
public enum UserStatus { | ||
NORMAL, DELETED, REPORTED, BLACKLISTED | ||
|
||
} |
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
55 changes: 55 additions & 0 deletions
55
src/main/java/com/catcher/core/domain/entity/UserStatusChangeHistory.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,55 @@ | ||
package com.catcher.core.domain.entity; | ||
|
||
import com.catcher.core.domain.UserStatus; | ||
import jakarta.persistence.*; | ||
import lombok.*; | ||
|
||
@Entity | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Builder | ||
@Table(name = "user_status_change_history") | ||
public class UserStatusChangeHistory extends BaseTimeEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "user_id", nullable = false) | ||
private User user; | ||
|
||
@OneToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "child_id") | ||
private UserStatusChangeHistory child; | ||
|
||
private UserStatus action; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "changed_user_id", nullable = false) | ||
private User changedUser; | ||
|
||
private String reason; | ||
|
||
private boolean affected; | ||
|
||
public static UserStatusChangeHistory create(User user, | ||
User changedUser, | ||
UserStatus action, | ||
String reason, | ||
boolean affected) { | ||
return UserStatusChangeHistory.builder() | ||
.user(user) | ||
.changedUser(changedUser) | ||
.action(action) | ||
.reason(reason) | ||
.affected(affected) | ||
.build(); | ||
} | ||
|
||
public void setChild(final UserStatusChangeHistory history) { | ||
this.child = history; | ||
} | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/catcher/core/port/UserStatusChangeHistoryRepository.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.catcher.core.port; | ||
|
||
import com.catcher.core.domain.UserStatus; | ||
import com.catcher.core.domain.entity.User; | ||
import com.catcher.core.domain.entity.UserStatusChangeHistory; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
public interface UserStatusChangeHistoryRepository { | ||
|
||
UserStatusChangeHistory save(UserStatusChangeHistory userStatusChangeHistory); | ||
|
||
Optional<UserStatusChangeHistory> findFirstByUserAndActionAndAffectedOrderByIdDesc(User user, UserStatus userStatus, boolean affected); | ||
|
||
List<UserStatusChangeHistory> findAllByUserId(Long id); | ||
|
||
} |
101 changes: 101 additions & 0 deletions
101
src/main/java/com/catcher/core/service/AdminUserService.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,101 @@ | ||
package com.catcher.core.service; | ||
|
||
import com.catcher.common.exception.BaseException; | ||
import com.catcher.common.exception.BaseResponseStatus; | ||
import com.catcher.core.db.UserRepository; | ||
import com.catcher.core.domain.UserSearchFilterType; | ||
import com.catcher.core.domain.UserStatus; | ||
import com.catcher.core.domain.entity.User; | ||
import com.catcher.core.domain.entity.UserStatusChangeHistory; | ||
import com.catcher.core.port.UserStatusChangeHistoryRepository; | ||
import com.catcher.resource.response.AdminUserCountPerDayResponse; | ||
import com.catcher.resource.response.AdminUserDetailResponse; | ||
import com.catcher.resource.response.AdminUserSearchResponse; | ||
import com.catcher.resource.response.AdminUserTotalCountResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.time.LocalDate; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class AdminUserService { | ||
|
||
private final UserRepository userRepository; | ||
|
||
private final UserStatusChangeHistoryRepository userStatusChangeHistoryRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public AdminUserTotalCountResponse getTotalUserCountInfo() { | ||
|
||
Long totalUserCount = userRepository.count(); | ||
Long deletedUserCount = userRepository.countByDeletedAtIsNotNull(); | ||
|
||
return AdminUserTotalCountResponse.create(totalUserCount, deletedUserCount); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public AdminUserCountPerDayResponse getUserCountPerDay(final LocalDate startDate, final LocalDate endDate) { | ||
validateTimeInput(startDate, endDate); | ||
|
||
final var newUsersDateCountMap = userRepository.countNewUsersPerDay(startDate, endDate); | ||
final var deletedUsersDateCountMap = userRepository.countDeletedUsersPerDay(startDate, endDate); | ||
final var reportedUsersDateCountMap = userRepository.countReportedUsersPerDay(startDate, endDate); | ||
|
||
return AdminUserCountPerDayResponse.create(newUsersDateCountMap, deletedUsersDateCountMap, reportedUsersDateCountMap); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public Page<AdminUserSearchResponse> searchUser(UserSearchFilterType filterType, | ||
LocalDate startDate, | ||
LocalDate endDate, | ||
String query, | ||
Pageable pageable) { | ||
final var userPage = userRepository.searchUsersWithFilter(filterType, startDate, endDate, query, pageable); | ||
|
||
return userPage.map(AdminUserSearchResponse::create); | ||
} | ||
|
||
private void validateTimeInput(final LocalDate startDate, final LocalDate endDate) { | ||
if (startDate.isAfter(endDate) || startDate.isAfter(LocalDate.now()) || endDate.isBefore(LocalDate.now())) { | ||
throw new BaseException(BaseResponseStatus.INVALID_DATE_INPUT); | ||
} | ||
} | ||
|
||
@Transactional | ||
public void changeUserStatus(final Long userId, final Long adminUserId, final UserStatus afterStatus, final String reason) { | ||
final User user = userRepository.findById(userId).orElseThrow(() -> new BaseException(BaseResponseStatus.DATA_NOT_FOUND)); | ||
final User adminUser = userRepository.findById(adminUserId).orElseThrow(() -> new BaseException(BaseResponseStatus.DATA_NOT_FOUND)); | ||
|
||
final UserStatus beforeStatus = user.getStatus(); | ||
final boolean affected = user.changeUserStatus(afterStatus); | ||
|
||
var parent = userStatusChangeHistoryRepository.findFirstByUserAndActionAndAffectedOrderByIdDesc(user, beforeStatus, true).orElse(null); | ||
|
||
UserStatusChangeHistory history = UserStatusChangeHistory.create(user, adminUser, afterStatus, reason, affected); | ||
history = userStatusChangeHistoryRepository.save(history); | ||
if (parent != null) { | ||
parent.setChild(history); | ||
} | ||
} | ||
|
||
public void validateUserCountDateInput(final LocalDate startDate, final LocalDate endDate) { | ||
if (startDate.isAfter(endDate)) { | ||
throw new BaseException(BaseResponseStatus.INVALID_DATE_INPUT); | ||
} | ||
|
||
if (startDate.plusMonths(3).isAfter(endDate)) { | ||
throw new BaseException(BaseResponseStatus.THREE_MONTHS_DATE_RANGE_EXCEPTION); | ||
} | ||
} | ||
|
||
public AdminUserDetailResponse searchUserDetail(final Long userId) { | ||
final var user = userRepository.findById(userId).orElseThrow(() -> new BaseException(BaseResponseStatus.DATA_NOT_FOUND)); | ||
|
||
return AdminUserDetailResponse.create(user); | ||
} | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
src/main/java/com/catcher/datasource/repository/UserStatusChangeHistoryJpaRepository.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,17 @@ | ||
package com.catcher.datasource.repository; | ||
|
||
import com.catcher.core.domain.UserStatus; | ||
import com.catcher.core.domain.entity.User; | ||
import com.catcher.core.domain.entity.UserStatusChangeHistory; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
public interface UserStatusChangeHistoryJpaRepository extends JpaRepository<UserStatusChangeHistory, Long> { | ||
|
||
Optional<UserStatusChangeHistory> findFirstByUserAndActionAndAffectedOrderByIdDesc(User user, UserStatus userStatus, boolean affected); | ||
|
||
List<UserStatusChangeHistory> findByUserId(Long userId); | ||
|
||
} |
39 changes: 39 additions & 0 deletions
39
src/main/java/com/catcher/datasource/user/UserJpaRepository.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 |
---|---|---|
@@ -1,11 +1,50 @@ | ||
package com.catcher.datasource.user; | ||
|
||
import com.catcher.core.domain.entity.User; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.jpa.domain.Specification; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.data.jpa.repository.Query; | ||
import org.springframework.data.repository.query.Param; | ||
|
||
import java.time.LocalDateTime; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
public interface UserJpaRepository extends JpaRepository<User, Long> { | ||
|
||
Optional<User> findByUsername(String username); | ||
|
||
Long countByDeletedAtIsNotNull(); | ||
|
||
Page<User> findAll(Specification<User> specification, Pageable pageable); | ||
|
||
@Query("SELECT new map(date(u.createdAt) as date, COUNT(u) as count) " + | ||
"FROM User u " + | ||
"WHERE u.createdAt BETWEEN :startDate AND :endDate " + | ||
"GROUP BY date(u.createdAt)") | ||
List<Map<String, Object>> countNewUsersPerDay(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); | ||
|
||
@Query("SELECT new map(date(u.createdAt) as date, COUNT(u) as count) " + | ||
"FROM User u " + | ||
"WHERE u.deletedAt BETWEEN :startDate AND :endDate " + | ||
"GROUP BY date(u.createdAt)") | ||
List<Map<String, Object>> countDeletedUsersPerDay(@Param("startDate") ZonedDateTime startDate, @Param("endDate") ZonedDateTime endDate); | ||
|
||
default List<Map<String, Object>> countDeletedUsersPerDay(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate) { | ||
return countDeletedUsersPerDay(startDate.atZone(ZoneId.systemDefault()), endDate.atZone(ZoneId.systemDefault())); | ||
} | ||
|
||
@Query("SELECT new map(date(ush.createdAt) as date, COUNT(DISTINCT u) as count) " + | ||
"FROM UserStatusChangeHistory ush " + | ||
"JOIN ush.user u " + | ||
"WHERE ush.createdAt BETWEEN :startDate AND :endDate " + | ||
"AND ush.action = com.catcher.core.domain.UserStatus.REPORTED " + | ||
"GROUP BY date(ush.createdAt)") | ||
List<Map<String, Object>> countReportedUsersPerDay(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); | ||
|
||
} |
Oops, something went wrong.