Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6f88c81
feat: users 테이블에 is_private 컬럼 추가
redjungi09 Apr 12, 2026
48dc349
feat: User 도메인에 isPrivate 필드 및 updatePrivacy() 추가
redjungi09 Apr 12, 2026
27b4cf6
feat: 비공개 프로필 접근 예외(ProfilePrivatedException) 추가
redjungi09 Apr 12, 2026
d02f33f
feat: 다른 유저 프로필 조회 API 추가 (GET /api/users/{userId})
redjungi09 Apr 12, 2026
d3c372c
feat: 다른 유저 보유 아이템 조회 API 추가 (GET /api/users/{userId}/items)
redjungi09 Apr 12, 2026
8ef9f2e
feat: 다른 유저 GitHub 활동 조회 API 추가 (GET /api/users/{userId}/github)
redjungi09 Apr 12, 2026
a31eea3
feat: 다른 유저 라이벌 조회 API 추가 (GET /api/users/{userId}/rivals)
redjungi09 Apr 12, 2026
3fd8988
feat: 프로필 비공개 설정 API 추가 (PATCH /api/users/me/privacy)
redjungi09 Apr 12, 2026
7847196
feat: 다른 유저 프로필 조회 dto추가
redjungi09 Apr 12, 2026
2825c72
feat: UserController에 다른 유저 조회 및 비공개 설정 엔드포인트 연결
redjungi09 Apr 12, 2026
5bc43fa
docs: 다른 유저 프로필 조회 스웨거 문서 작성
redjungi09 Apr 12, 2026
9aae91d
test: 다른 유저 프로필 조회 및 비공개 설정 서비스 테스트 추가
redjungi09 Apr 12, 2026
4d0c55a
test: User 도메인 isPrivate 필드 추가에 따른 기존 테스트 생성자 수정
redjungi09 Apr 12, 2026
d7f3b67
fix: insigma 필드 하위 호환 유지를 위한 deprecated 처리
redjungi09 Apr 12, 2026
00ab1f6
fix: 탈퇴 유저 조회 시 UserNotFoundException 처리 추가
redjungi09 Apr 12, 2026
fb012b5
test: 탈퇴 유저 및 유저 라이벌 조회 테스트 추가
redjungi09 Apr 12, 2026
e4cfadd
fix: GetUserProfileService 빈 이름 충돌 해결 (GetOtherUserProfileService로 변경)
redjungi09 Apr 12, 2026
4ac412b
fix: 마이그레이션 버전 V32 → V33으로 변경
redjungi09 Apr 12, 2026
d6d2be0
fix: 마이그레이션 버전 V33 -> V34으로 변경
redjungi09 Apr 12, 2026
c6f2de4
fix: 탈퇴한 라이벌 상대방 조회 시 전체 요청 실패 방지
redjungi09 Apr 12, 2026
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 @@ -72,6 +72,9 @@ public class UserJpaEntity {

private Instant deletedAt;

@Column(nullable = false)
private boolean isPrivate;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private RankTier currentRankTier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public UserJpaEntity toJpaEntity(User user) {
user.major(),
user.userStatus(),
user.deletedAt(),
user.isPrivate(),
user.currentRankTier(),
user.currentExpTier()
);
Expand All @@ -43,6 +44,7 @@ public User toDomain(UserJpaEntity userJpaEntity) {
userJpaEntity.getMajor(),
userJpaEntity.getUserStatus(),
userJpaEntity.getDeletedAt(),
userJpaEntity.isPrivate(),
userJpaEntity.getCurrentRankTier(),
userJpaEntity.getCurrentExpTier()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,42 @@
import com.process.clash.adapter.web.user.dto.GetMyActivityCalendarDto;
import com.process.clash.adapter.web.user.dto.GetMyItemsDto;
import com.process.clash.adapter.web.user.dto.GetMyProfileDto;
import com.process.clash.adapter.web.user.dto.GetUserProfileDto;
import com.process.clash.adapter.web.user.dto.UpdateMyPrivacyDto;
import com.process.clash.adapter.web.user.dto.IssueProfileImageUploadUrlDto;
import com.process.clash.adapter.web.user.dto.LinkGitHubOAuthDto;
import com.process.clash.adapter.web.user.dto.UpdateMyProfileImageDto;
import com.process.clash.adapter.web.security.AuthenticatedActor;
import com.process.clash.adapter.web.compete.rival.rival.dto.GetMyRivalActingDto;
import com.process.clash.application.common.actor.Actor;
import com.process.clash.application.compete.rival.rival.data.GetMyRivalActingData;
import com.process.clash.application.profile.data.EquipMyItemData;
import com.process.clash.application.profile.data.GetMyGitHubActivityDetailData;
import com.process.clash.application.profile.data.GetMyGitHubActivityData;
import com.process.clash.application.profile.data.GetMyActivityCalendarData;
import com.process.clash.application.profile.data.GetMyItemsData;
import com.process.clash.application.profile.data.GetMyProfileData;
import com.process.clash.application.profile.data.GetUserProfileData;
import com.process.clash.application.profile.data.GetUserItemsData;
import com.process.clash.application.profile.data.GetUserGitHubActivityData;
import com.process.clash.application.profile.data.GetUserRivalsData;
import com.process.clash.application.profile.data.UnequipMyItemData;
import com.process.clash.application.profile.port.in.EquipMyItemUsecase;
import com.process.clash.application.profile.port.in.GetMyGitHubActivityDetailUsecase;
import com.process.clash.application.profile.port.in.GetMyGitHubActivityUsecase;
import com.process.clash.application.profile.port.in.GetMyActivityCalendarUsecase;
import com.process.clash.application.profile.port.in.GetMyItemsUsecase;
import com.process.clash.application.profile.port.in.GetMyProfileUsecase;
import com.process.clash.application.profile.port.in.GetUserProfileUsecase;
import com.process.clash.application.profile.port.in.GetUserItemsUsecase;
import com.process.clash.application.profile.port.in.GetUserGitHubActivityUsecase;
import com.process.clash.application.profile.port.in.GetUserRivalsUsecase;
import com.process.clash.application.profile.port.in.UnequipMyItemUsecase;
import com.process.clash.application.user.user.data.IssueProfileImageUploadUrlData;
import com.process.clash.application.user.user.data.UpdateMyPrivacyData;
import com.process.clash.application.user.user.data.UpdateMyProfileImageData;
import com.process.clash.application.user.user.port.in.IssueProfileImageUploadUrlUseCase;
import com.process.clash.application.user.user.port.in.UpdateMyPrivacyUseCase;
import com.process.clash.application.user.user.port.in.UpdateMyProfileImageUseCase;
import com.process.clash.application.user.user.port.in.WithdrawUseCase;
import com.process.clash.application.user.usergithub.data.GetMyGitHubLinkStatusData;
Expand Down Expand Up @@ -71,6 +85,11 @@ public class UserController implements UserControllerDocument {
private final IssueProfileImageUploadUrlUseCase issueProfileImageUploadUrlUseCase;
private final UpdateMyProfileImageUseCase updateMyProfileImageUseCase;
private final WithdrawUseCase withdrawUseCase;
private final GetUserProfileUsecase getUserProfileUsecase;
private final GetUserItemsUsecase getUserItemsUsecase;
private final GetUserGitHubActivityUsecase getUserGitHubActivityUsecase;
private final GetUserRivalsUsecase getUserRivalsUsecase;
private final UpdateMyPrivacyUseCase updateMyPrivacyUseCase;

@DeleteMapping("/me")
public ApiResponse<Void> withdraw(
Expand Down Expand Up @@ -198,4 +217,61 @@ public ApiResponse<UpdateMyProfileImageDto.Response> updateMyProfileImage(
UpdateMyProfileImageDto.Response response = UpdateMyProfileImageDto.Response.from(result);
return ApiResponse.success(response, "프로필 이미지 수정을 성공했습니다.");
}

@GetMapping("/{userId}")
public ApiResponse<GetUserProfileDto.Response> getUserProfile(
@AuthenticatedActor Actor actor,
@PathVariable Long userId
) {
GetUserProfileData.Command command = new GetUserProfileData.Command(userId);
GetUserProfileData.Result result = getUserProfileUsecase.execute(command);
GetUserProfileDto.Response response = GetUserProfileDto.Response.from(result);
return ApiResponse.success(response, "유저 프로필을 성공적으로 조회했습니다.");
}

@GetMapping("/{userId}/items")
public ApiResponse<GetMyItemsDto.Response> getUserItems(
@AuthenticatedActor Actor actor,
@PathVariable Long userId,
@RequestParam(name = "category", defaultValue = "ALL") UserItemCategory category
) {
GetUserItemsData.Command command = new GetUserItemsData.Command(userId, category);
GetMyItemsData.Result result = getUserItemsUsecase.execute(command);
GetMyItemsDto.Response response = GetMyItemsDto.Response.from(result);
return ApiResponse.success(response, "유저 아이템 목록을 성공적으로 조회했습니다.");
}

@GetMapping("/{userId}/github")
public ApiResponse<GetMyGitHubActivityDto.Response> getUserGitHubActivity(
@AuthenticatedActor Actor actor,
@PathVariable Long userId,
@RequestParam PeriodCategory period
) {
GetUserGitHubActivityData.Command command = new GetUserGitHubActivityData.Command(userId, period);
GetMyGitHubActivityData.Result result = getUserGitHubActivityUsecase.execute(command);
GetMyGitHubActivityDto.Response response = GetMyGitHubActivityDto.Response.from(result);
return ApiResponse.success(response, "유저 깃허브 활동을 성공적으로 조회했습니다.");
}

@GetMapping("/{userId}/rivals")
public ApiResponse<GetMyRivalActingDto.Response> getUserRivals(
@AuthenticatedActor Actor actor,
@PathVariable Long userId
) {
GetUserRivalsData.Command command = new GetUserRivalsData.Command(userId);
GetMyRivalActingData.Result result = getUserRivalsUsecase.execute(command);
GetMyRivalActingDto.Response response = GetMyRivalActingDto.Response.from(result);
return ApiResponse.success(response, "유저 라이벌 목록을 성공적으로 조회했습니다.");
}

@PatchMapping("/me/privacy")
public ApiResponse<UpdateMyPrivacyDto.Response> updateMyPrivacy(
@AuthenticatedActor Actor actor,
@Valid @RequestBody UpdateMyPrivacyDto.Request request
) {
UpdateMyPrivacyData.Command command = request.toCommand(actor);
UpdateMyPrivacyData.Result result = updateMyPrivacyUseCase.execute(command);
UpdateMyPrivacyDto.Response response = UpdateMyPrivacyDto.Response.from(result);
return ApiResponse.success(response, "프로필 공개 설정을 성공적으로 변경했습니다.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
import com.process.clash.adapter.web.user.docs.response.IssueProfileImageUploadUrlResponseDocument;
import com.process.clash.adapter.web.user.docs.response.LinkGitHubOAuthResponseDocument;
import com.process.clash.adapter.web.user.docs.response.UpdateMyProfileImageResponseDocument;
import com.process.clash.adapter.web.compete.rival.rival.dto.GetMyRivalActingDto;
import com.process.clash.adapter.web.user.dto.EquippedItemsDto;
import com.process.clash.adapter.web.user.dto.GetMyGitHubLinkStatusDto;
import com.process.clash.adapter.web.user.dto.GetMyGitHubActivityDto;
import com.process.clash.adapter.web.user.dto.GetMyGitHubActivityDetailDto;
import com.process.clash.adapter.web.user.dto.GetMyItemsDto;
import com.process.clash.adapter.web.user.dto.GetMyActivityCalendarDto;
import com.process.clash.adapter.web.user.dto.GetMyProfileDto;
import com.process.clash.adapter.web.user.dto.GetUserProfileDto;
import com.process.clash.adapter.web.user.dto.UpdateMyPrivacyDto;
import com.process.clash.adapter.web.user.dto.IssueProfileImageUploadUrlDto;
import com.process.clash.adapter.web.user.dto.LinkGitHubOAuthDto;
import com.process.clash.adapter.web.user.dto.UpdateMyProfileImageDto;
Expand Down Expand Up @@ -252,4 +255,75 @@ com.process.clash.adapter.web.common.ApiResponse<UpdateMyProfileImageDto.Respons
))
UpdateMyProfileImageDto.Request request
);

@Operation(summary = "다른 유저 프로필 조회", description = "특정 유저의 공개 프로필 정보를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = GetUserProfileDto.Response.class)))
})
com.process.clash.adapter.web.common.ApiResponse<GetUserProfileDto.Response> getUserProfile(
@Parameter(hidden = true) Actor actor,
@Parameter(description = "유저 ID", required = true) Long userId
);

@Operation(summary = "다른 유저 보유 아이템 조회", description = "특정 유저의 보유 아이템 목록을 조회합니다. 비공개 프로필이면 403.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = GetMyItemsDto.Response.class))),
@ApiResponse(responseCode = "403", description = "비공개 프로필")
})
com.process.clash.adapter.web.common.ApiResponse<GetMyItemsDto.Response> getUserItems(
@Parameter(hidden = true) Actor actor,
@Parameter(description = "유저 ID", required = true) Long userId,
@Parameter(description = "카테고리 (ALL, INSIGNIA, NAMEPLATE, BANNER)", required = false) UserItemCategory category
);

@Operation(summary = "다른 유저 GitHub 활동 조회", description = "특정 유저의 GitHub 활동을 조회합니다. 비공개 프로필이면 403.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = GetMyGitHubActivityDto.Response.class))),
@ApiResponse(responseCode = "403", description = "비공개 프로필")
})
com.process.clash.adapter.web.common.ApiResponse<GetMyGitHubActivityDto.Response> getUserGitHubActivity(
@Parameter(hidden = true) Actor actor,
@Parameter(description = "유저 ID", required = true) Long userId,
@Parameter(description = "기간 (YEAR, MONTH, WEEK)", required = true) PeriodCategory period
);

@Operation(summary = "다른 유저 라이벌 조회", description = "특정 유저의 라이벌 목록을 조회합니다. 비공개 프로필이면 403.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공",
content = @Content(schema = @Schema(implementation = GetMyRivalActingDto.Response.class))),
@ApiResponse(responseCode = "403", description = "비공개 프로필")
})
com.process.clash.adapter.web.common.ApiResponse<GetMyRivalActingDto.Response> getUserRivals(
@Parameter(hidden = true) Actor actor,
@Parameter(description = "유저 ID", required = true) Long userId
);

@Operation(summary = "프로필 공개 설정 변경", description = "내 프로필의 공개/비공개 여부를 설정합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "변경 성공",
content = @Content(schema = @Schema(implementation = UpdateMyPrivacyDto.Response.class),
examples = @ExampleObject(value = """
{
"success": true,
"message": "프로필 공개 설정을 성공적으로 변경했습니다.",
"data": {
"isPrivate": true
}
}
""")))
})
com.process.clash.adapter.web.common.ApiResponse<UpdateMyPrivacyDto.Response> updateMyPrivacy(
@Parameter(hidden = true) Actor actor,
@RequestBody(description = "비공개 설정", required = true,
content = @Content(schema = @Schema(implementation = UpdateMyPrivacyDto.Request.class),
examples = @ExampleObject(value = """
{
"isPrivate": true
}
""")))
UpdateMyPrivacyDto.Request request
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.process.clash.adapter.web.user.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.process.clash.application.profile.data.EquippedItemsData;

public class EquippedItemsDto {
Expand All @@ -21,6 +22,13 @@ public static Response from(EquippedItemsData data) {
Item.from(data.bgm())
);
}

/** @deprecated insignia 필드로 대체됨. 클라이언트 업데이트 후 제거 예정 */
@Deprecated
@JsonProperty("insigma")
public Item insigma() {
return insignia;
}
}

public record Item(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.process.clash.adapter.web.user.dto;

import com.process.clash.application.profile.data.GetUserProfileData;
import com.process.clash.application.realtime.data.UserActivityStatus;
import com.process.clash.domain.common.enums.Major;

public class GetUserProfileDto {

public record Response(
Long id,
String username,
String name,
String profileImage,
Major major,
String tier,
UserActivityStatus activityStatus,
boolean isPrivate,
EquippedItemsDto.Response equippedItems
) {
public static Response from(GetUserProfileData.Result result) {
return new Response(
result.id(),
result.username(),
result.name(),
result.profileImage(),
result.major(),
result.tier(),
result.activityStatus(),
result.isPrivate(),
EquippedItemsDto.Response.from(result.equippedItems())
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.process.clash.adapter.web.user.dto;

import com.process.clash.application.common.actor.Actor;
import com.process.clash.application.user.user.data.UpdateMyPrivacyData;
import jakarta.validation.constraints.NotNull;

public class UpdateMyPrivacyDto {

public record Request(
@NotNull Boolean isPrivate
) {
public UpdateMyPrivacyData.Command toCommand(Actor actor) {
return new UpdateMyPrivacyData.Command(actor, isPrivate);
}
}

public record Response(
boolean isPrivate
) {
public static Response from(UpdateMyPrivacyData.Result result) {
return new Response(result.isPrivate());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.process.clash.application.profile.data;

import com.process.clash.domain.common.enums.PeriodCategory;

public class GetUserGitHubActivityData {

public record Command(
Long targetUserId,
PeriodCategory period
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.process.clash.application.profile.data;

import com.process.clash.domain.common.enums.UserItemCategory;

public class GetUserItemsData {

public record Command(
Long targetUserId,
UserItemCategory category
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.process.clash.application.profile.data;

import com.process.clash.application.realtime.data.UserActivityStatus;
import com.process.clash.domain.common.enums.Major;
import com.process.clash.domain.user.user.entity.User;

public class GetUserProfileData {

public record Command(
Long targetUserId
) {}

public record Result(
Long id,
String username,
String name,
String profileImage,
Major major,
String tier,
UserActivityStatus activityStatus,
boolean isPrivate,
EquippedItemsData equippedItems
) {
public static Result from(
User user,
UserActivityStatus activityStatus,
EquippedItemsData equippedItems
) {
return new Result(
user.id(),
user.username(),
user.name(),
user.profileImage(),
user.major(),
user.tier(),
activityStatus,
user.isPrivate(),
equippedItems
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.process.clash.application.profile.data;

public class GetUserRivalsData {

public record Command(
Long targetUserId
) {}
}
Loading
Loading