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
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public String getName() {
@Override
public boolean isAccountNonLocked() {
// 계정 정지(밴) 상태인 경우 잠금(Locked) 처리
return !Boolean.TRUE.equals(userResponse.banned());
return !Boolean.TRUE.equals(userResponse.isBanned());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
.orElseGet(() -> registerSocialUser(attributes, socialType));

return new MoMoGoUserDetails(
new UserResponse(
user.getId(),
user.getEmail(),
user.getName(),
user.getProfileImageUrl(),
user.getRole(),
user.getSocial(),
user.getIsBanned(),
user.getCreatedAt(),
user.getDeletedAt()
),
UserResponse.builder()
.id(user.getId())
.email(user.getEmail())
.name(user.getName())
.profileImageUrl(user.getProfileImageUrl())
.role(user.getRole())
.social(user.getSocial())
.isBanned(user.getIsBanned())
.createdAt(user.getCreatedAt())
.deletedAt(user.getDeletedAt())
.build(),
user.getPassword(),
attributes.attributes()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,13 @@ public MoMoGoUserDetails parseAccessToken(String token) {
role = UserRole.valueOf(roleString);
}

UserResponse userResponse = new UserResponse(
userId,
userEmail,
name,
null,
role,
null,
false,
null,
null
);
UserResponse userResponse = UserResponse.builder()
.id(userId)
.email(userEmail)
.name(name)
.role(role)
.isBanned(false)
.build();

log.info("[TokenProvider] parseAccessToken 완료: UserResponse 생성");
return new MoMoGoUserDetails(userResponse, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.momogo.core.domain.user.dto;

import com.momogo.core.domain.user.entity.enums.UserRole;
import org.hibernate.query.SortDirection;

import java.util.UUID;

public record UserSearchCondition(
String nameLike,
String emailLike,
String spaceNameLike,
UserRole role,
String cursor,
UUID idAfter,
SortDirection sortDirection,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.momogo.core.domain.user.dto.request;

import com.momogo.core.domain.user.entity.enums.UserRole;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
Expand All @@ -11,6 +12,8 @@ public record UserPageRequest(

String nameLike,
String emailLike,
String spaceNameLike,
UserRole role,
String cursor, // 가입일(OffsetDateTime) 문자열
UUID idAfter, // 보조 식별자 유저 ID

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
import com.momogo.core.domain.user.entity.enums.SocialType;
import com.momogo.core.domain.user.entity.enums.UserRole;

import lombok.Builder;

import java.time.OffsetDateTime;
import java.util.UUID;

@Builder
public record UserResponse(
UUID id,
String email,
String name,
String profileImageUrl,
UserRole role,
SocialType social,
Boolean banned,
Boolean isBanned,
OffsetDateTime createdAt,
OffsetDateTime deletedAt
OffsetDateTime updatedAt,
OffsetDateTime deletedAt,
UUID spaceId,
String spaceName
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ public abstract class UserMapper {
@Value("${app.file.base-url}")
protected String fileBaseUrl;

@Mapping(target = "banned", source = "isBanned")
@Mapping(target = "profileImageUrl", source = "profileImageUrl", qualifiedByName = "resolveImageUrl")
@Mapping(target = "spaceId", source = "space.id")
@Mapping(target = "spaceName", source = "space.name")
public abstract UserResponse toResponse(User user);

// 이미지 파일명을 클라이언트가 접근할 수 있는 완전한 URL 형태로 변환합니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;

import static com.momogo.core.domain.space.entity.QSpace.space;
import static com.momogo.core.domain.user.entity.QUser.user;

@Slf4j
Expand All @@ -34,9 +36,12 @@ public class UserRepositoryImpl implements UserRepositoryCustom {
public List<User> findAllByCursor(UserSearchCondition condition, Pageable pageable) {
return queryFactory
.selectFrom(user)
.leftJoin(user.space, space).fetchJoin()
.where(
nameLike(condition.nameLike()),
emailLike(condition.emailLike()),
spaceNameLike(condition.spaceNameLike()),
roleEq(condition.role()),
// 정렬 기준에 따라 활성/탈퇴 회원을 동적으로 분기하여 필터링
filterBySortBy(condition.sortBy()),
cursorCondition(condition.sortBy(), condition.cursor(), condition.idAfter(), condition.sortDirection())
Expand All @@ -51,21 +56,32 @@ public long countByCondition(UserSearchCondition condition) {
Long count = queryFactory
.select(user.count())
.from(user)
.leftJoin(user.space, space)
.where(
nameLike(condition.nameLike()),
emailLike(condition.emailLike()),
spaceNameLike(condition.spaceNameLike()),
roleEq(condition.role()),
filterBySortBy(condition.sortBy())
)
.fetchOne();
return count != null ? count : 0L;
}

private BooleanExpression roleEq(UserRole role) {
return role != null ? user.role.eq(role) : null;
}

private BooleanExpression spaceNameLike(String spaceNameLike) {
return spaceNameLike != null && !spaceNameLike.isBlank() ? space.name.containsIgnoreCase(spaceNameLike) : null;
}

private BooleanExpression nameLike(String nameLike) {
return nameLike != null && !nameLike.isBlank() ? user.name.contains(nameLike) : null;
return nameLike != null && !nameLike.isBlank() ? user.name.containsIgnoreCase(nameLike) : null;
}

private BooleanExpression emailLike(String emailLike) {
return emailLike != null && !emailLike.isBlank() ? user.email.contains(emailLike) : null;
return emailLike != null && !emailLike.isBlank() ? user.email.containsIgnoreCase(emailLike) : null;
}

private DateTimePath<OffsetDateTime> getSortPath(String sortBy) {
Expand All @@ -84,7 +100,7 @@ private BooleanExpression cursorCondition(String sortBy, String cursor, UUID idA

final OffsetDateTime cursorTime;
try {
cursorTime = OffsetDateTime.parse(cursor);
cursorTime = OffsetDateTime.parse(cursor).truncatedTo(ChronoUnit.MICROS);
} catch (DateTimeParseException e) {
log.warn("유효하지 않은 형식의 커서 전달됨: {}", cursor);
return null;
Expand All @@ -101,13 +117,17 @@ private BooleanExpression cursorCondition(String sortBy, String cursor, UUID idA
.or(sortPath.eq(cursorTime).and(user.id.lt(idAfter)));
}

// 계정을 삭제하지 않은 유저는 deletedAt = null이므로
// Super Admin이 deletedAt을 필터 조건으로 사용했을 경우 deletedAt != null인 삭제 요청한 유저만 확인할 수 있다.
// 계정을 삭제하지 않은 유저는 deletedAt = null이므로
// deletedAt을 필터 조건으로 사용했을 경우 deletedAt != null인 삭제 요청한 유저만 확인한다.
// updatedAt 정렬 시에는 활성 유저만 확인하고, createdAt 정렬 시에는 탈퇴 유저를 포함한 전체 회원을 조회한다.
private BooleanExpression filterBySortBy(String sortBy) {
if ("deletedAt".equals(sortBy)) {
return user.deletedAt.isNotNull();
}
return user.deletedAt.isNull();
if ("updatedAt".equals(sortBy)) {
return user.deletedAt.isNull();
}
return null;
}

// 1차 정렬 조건 필드와 2차 순서 보장용 고유 ID(UUID) 정렬을 결합하여 복합 정렬 순서 정의
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;

Expand Down Expand Up @@ -277,6 +277,8 @@ public CursorResponse<UserResponse> findAllUsers(UserPageRequest request) {
UserSearchCondition condition = new UserSearchCondition(
request.nameLike(),
request.emailLike(),
request.spaceNameLike(),
request.role(),
request.cursor(),
request.idAfter(),
request.sortDirection(),
Expand All @@ -303,7 +305,9 @@ public CursorResponse<UserResponse> findAllUsers(UserPageRequest request) {
default -> lastUser.getCreatedAt();
};

nextCursor = sortTime != null ? sortTime.toString() : null;
if (sortTime != null) {
nextCursor = sortTime.truncatedTo(ChronoUnit.MICROS).toString();
}
nextIdAfter = lastUser.getId();
}

Expand Down
3 changes: 2 additions & 1 deletion momogo-frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>momogo-frontend</title>
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
<title>MoMoGo - 에듀테크 인터랙티브 플랫폼</title>
</head>
<body>
<div id="root"></div>
Expand Down
Loading