Skip to content
Open
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 @@ -15,6 +15,7 @@
import flipnote.group.api.dto.request.CreateGroupRequestDto;
import flipnote.group.api.dto.response.ChangeGroupResponseDto;
import flipnote.group.api.dto.response.CreateGroupResponseDto;
import flipnote.group.api.dto.response.FindGroupMemberResponseDto;
import flipnote.group.api.dto.response.FindGroupResponseDto;
import flipnote.group.application.port.in.ChangeGroupUseCase;
import flipnote.group.application.port.in.CreateGroupUseCase;
Expand All @@ -24,6 +25,7 @@
import flipnote.group.application.port.in.command.CreateGroupCommand;
import flipnote.group.application.port.in.command.DeleteGroupCommand;
import flipnote.group.application.port.in.command.FindGroupCommand;
import flipnote.group.application.port.in.command.FindGroupMemberCommand;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

Expand Down Expand Up @@ -125,24 +127,20 @@ public ResponseEntity<FindGroupResponseDto> findGroup(
@DeleteMapping("/{groupId}")
public ResponseEntity<Void> deleteGroup(
@RequestHeader("X-USER-ID") Long userId,
@PathVariable("groupId") Long groupId
) {
@PathVariable("groupId") Long groupId) {

DeleteGroupCommand cmd = new DeleteGroupCommand(userId, groupId);

deleteGroupUseCase.deleteGroup(cmd);

return ResponseEntity.noContent().build();
}

//todo 그룹 내 멤버 조회

//todo 그룹 전체 조회

//todo 내 그룹 전체 조회

//todo 내가 생성한 그룹 전체 조회

//todo 하위 권한 수정

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package flipnote.group.adapter.in.web;

public class InvitationController {
//todo 가인 신청 요청

//todo 그룹 내 가입 신청한 리스트 조회

//todo 가입신청 응답

//todo 가입신청 삭제

//todo 내가 신청한 가입신청 리스트 조회

//todo 초대

//todo 그룹 멤버 추방

}
48 changes: 37 additions & 11 deletions src/main/java/flipnote/group/adapter/in/web/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
package flipnote.group.adapter.in.web;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import flipnote.group.api.dto.response.FindGroupMemberResponseDto;
import flipnote.group.application.port.in.FindGroupMemberUseCase;
import flipnote.group.application.port.in.command.FindGroupMemberCommand;
import flipnote.group.application.port.in.result.FindGroupMemberResult;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@RestController
@RequestMapping("/v1/groups/{groupId}")
public class MemberController {
//todo 가인 신청 요청

//todo 그룹 내 가입 신청한 리스트 조회

//todo 가입신청 응답

//todo 가입신청 삭제

//todo 내가 신청한 가입신청 리스트 조회

//todo 초대
private final FindGroupMemberUseCase findGroupMemberUseCase;

/**
* 그룹 내 멤버 전체 조회
* @param userId
* @param groupId
* @return
*/
@GetMapping("/members")
public ResponseEntity<FindGroupMemberResponseDto> findGroupMembers(
@RequestHeader("X-USER-ID") Long userId,
@PathVariable("groupId") Long groupId) {

FindGroupMemberCommand cmd = new FindGroupMemberCommand(userId, groupId);

FindGroupMemberResult result = findGroupMemberUseCase.findGroupMember(cmd);

FindGroupMemberResponseDto res = FindGroupMemberResponseDto.from(result);

//todo 그룹 멤버 추방
return ResponseEntity.ok(res);
}

//todo 하위 권한 수정
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
Expand Down Expand Up @@ -40,14 +43,15 @@ public class GroupMemberEntity extends BaseEntity {
@Column(name = "user_id", nullable = false)
private Long userId;

@Column(name = "group_role_id", nullable = false)
private Long groupRoleId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_role_id", nullable = false)
private RoleEntity role;

@Builder
private GroupMemberEntity(Long groupId, Long userId, Long groupRoleId) {
private GroupMemberEntity(Long groupId, Long userId, RoleEntity role) {
this.groupId = groupId;
this.userId = userId;
this.groupRoleId = groupRoleId;
this.role = role;
}

/**
Expand All @@ -56,11 +60,11 @@ private GroupMemberEntity(Long groupId, Long userId, Long groupRoleId) {
* @param userId
* @return
*/
public static GroupMemberEntity create(Long groupId, Long userId, Long groupRoleId) {
public static GroupMemberEntity create(Long groupId, Long userId, RoleEntity role) {
return GroupMemberEntity.builder()
.groupId(groupId)
.userId(userId)
.groupRoleId(groupRoleId)
.role(role)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
package flipnote.group.adapter.out.persistence;

import java.util.List;

import org.springframework.stereotype.Repository;

import flipnote.group.adapter.out.entity.GroupMemberEntity;
import flipnote.group.adapter.out.entity.RoleEntity;
import flipnote.group.adapter.out.persistence.mapper.GroupMemberMapper;
import flipnote.group.application.port.out.GroupMemberRepositoryPort;
import flipnote.group.domain.model.member.GroupMemberRole;
import flipnote.group.infrastructure.persistence.jpa.GroupMemberRepository;
import flipnote.group.domain.model.member.GroupMember;
import flipnote.group.domain.model.member.MemberInfo;
import flipnote.group.infrastructure.persistence.jpa.GroupMemberRepositoryRepository;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class GroupMemberRepositoryAdapter implements GroupMemberRepositoryPort {

private final GroupMemberRepository groupMemberRepository;
private final GroupMemberRepositoryRepository groupMemberRepository;

/**
* 그룹 멤버 저장
* @param groupId
* @param userId
*/
@Override
public void save(Long groupId, Long userId, Long roleId) {
groupMemberRepository.save(GroupMemberMapper.create(groupId, userId, roleId));
public void save(Long groupId, Long userId, RoleEntity role) {
groupMemberRepository.save(GroupMemberMapper.create(groupId, userId, role));
}

/**
Expand All @@ -31,7 +36,26 @@ public void save(Long groupId, Long userId, Long roleId) {
* @return
*/
@Override
public boolean existsUserInGroup(Long groupId, Long userId) {
return groupMemberRepository.existsByGroupIdAndUserId(groupId, userId);
public void existsUserInGroup(Long groupId, Long userId) {

boolean isMember = groupMemberRepository.existsByGroupIdAndUserId(groupId, userId);

if(!isMember) {
throw new IllegalArgumentException("user not in Group");
}
}

/**
* 그룹 멤버 아이디 조회
* @param groupId
* @return
*/
@Override
public List<MemberInfo> findMemberInfo(Long groupId) {
List<GroupMemberEntity> entities = groupMemberRepository.findAllByGroupId(groupId);

List<MemberInfo> memberInfo = GroupMemberMapper.toMemberInfo(entities);

return memberInfo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.stereotype.Repository;

Expand All @@ -11,7 +12,7 @@
import flipnote.group.application.port.out.GroupRoleRepositoryPort;
import flipnote.group.domain.model.member.GroupMemberRole;
import flipnote.group.domain.model.permission.GroupPermission;
import flipnote.group.infrastructure.persistence.jpa.GroupMemberRepository;
import flipnote.group.infrastructure.persistence.jpa.GroupMemberRepositoryRepository;
import flipnote.group.infrastructure.persistence.jpa.GroupRolePermissionRepository;
import flipnote.group.infrastructure.persistence.jpa.GroupRoleRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -22,7 +23,7 @@ public class GroupRoleRepositoryAdapter implements GroupRoleRepositoryPort {

private final GroupRoleRepository groupRoleRepository;
private final GroupRolePermissionRepository groupRolePermissionRepository;
private final GroupMemberRepository groupMemberRepository;
private final GroupMemberRepositoryRepository groupMemberRepository;

private static final Map<GroupMemberRole, List<GroupPermission>> DEFAULT_PERMS_BY_ROLE =
Map.of(
Expand All @@ -47,30 +48,33 @@ public class GroupRoleRepositoryAdapter implements GroupRoleRepositoryPort {
* @return
*/
@Override
public Long create(Long groupId) {
// 오너 역할 생성
Map<GroupMemberRole, Long> roleIdByRole = Arrays.stream(new GroupMemberRole[]{
public RoleEntity create(Long groupId) {
// 역할 생성
Map<GroupMemberRole, RoleEntity> roleEntityByRole =
Arrays.stream(new GroupMemberRole[]{
GroupMemberRole.OWNER,
GroupMemberRole.HEAD_MANAGER,
GroupMemberRole.MANAGER,
GroupMemberRole.MEMBER
})
.collect(java.util.stream.Collectors.toMap(
}).collect(Collectors.toMap(
role -> role,
role -> groupRoleRepository.save(RoleEntity.create(groupId, role)).getId()
role -> groupRoleRepository.save(RoleEntity.create(groupId, role))
));

// 역할별 기본 권한 세팅 (role-permission 매핑 생성)
// 권한 매핑 생성
List<PermissionEntity> perms = DEFAULT_PERMS_BY_ROLE.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.map(p -> PermissionEntity.create(roleIdByRole.get(e.getKey()), p))
.map(p -> PermissionEntity.create(
roleEntityByRole.get(e.getKey()).getId(), // roleId 사용
p
))
)
.toList();

groupRolePermissionRepository.saveAll(perms);

// 그룹 생성자에게 OWNER roleId 리턴 (바깥에서 group_members 생성할 때 사용)
return roleIdByRole.get(GroupMemberRole.OWNER);
return roleEntityByRole.get(GroupMemberRole.OWNER);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,56 @@
package flipnote.group.adapter.out.persistence.mapper;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.springframework.stereotype.Component;

import flipnote.group.adapter.out.entity.GroupMemberEntity;
import flipnote.group.adapter.out.entity.RoleEntity;
import flipnote.group.domain.model.member.GroupMember;
import flipnote.group.domain.model.member.GroupMemberRole;
import flipnote.group.domain.model.member.MemberInfo;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@Component
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GroupMemberMapper {
public static GroupMemberEntity create(Long groupId, Long userId, Long roleId) {
return GroupMemberEntity.create(groupId, userId, roleId);
public static GroupMemberEntity create(Long groupId, Long userId, RoleEntity role) {
return GroupMemberEntity.create(groupId, userId, role);
}

public static GroupMember toDomain(GroupMemberEntity entity) {

if (entity == null) {
return null;
}

return GroupMember.builder()
.id(entity.getId())
.groupId(entity.getGroupId())
.role(entity.getRole())
.build();
}
Comment on lines +24 to 35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

toDomain()에서 userId 매핑이 누락되었습니다 — 도메인 객체의 userId가 항상 null이 됩니다.

GroupMemberEntity에는 userId 필드가 있고, GroupMember 도메인 모델에도 userId 필드가 있지만, 빌더에서 .userId(entity.getUserId()) 호출이 빠져 있습니다.

🐛 수정 제안
 	return GroupMember.builder()
 		.id(entity.getId())
 		.groupId(entity.getGroupId())
+		.userId(entity.getUserId())
 		.role(entity.getRole())
 		.build();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static GroupMember toDomain(GroupMemberEntity entity) {
if (entity == null) {
return null;
}
return GroupMember.builder()
.id(entity.getId())
.groupId(entity.getGroupId())
.role(entity.getRole())
.build();
}
public static GroupMember toDomain(GroupMemberEntity entity) {
if (entity == null) {
return null;
}
return GroupMember.builder()
.id(entity.getId())
.groupId(entity.getGroupId())
.userId(entity.getUserId())
.role(entity.getRole())
.build();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java`
around lines 24 - 35, The toDomain method in GroupMemberMapper is missing
mapping for userId, causing GroupMember.userId to be null; update
GroupMemberMapper.toDomain to set the userId on the returned GroupMember by
reading entity.getUserId() (i.e., add the .userId(entity.getUserId()) call to
the GroupMember.builder() chain so GroupMemberEntity.userId is propagated into
the GroupMember domain object).


/**
* 그룹 멤버 정보 전체 조회
* @param entities
* @return
*/
public static List<MemberInfo> toMemberInfo(List<GroupMemberEntity> entities) {
if (entities == null || entities.isEmpty()) {
return Collections.emptyList();
}

return entities.stream()
.map(entity -> MemberInfo.builder()
.userId(entity.getUserId())
.role(entity.getRole().getRole())
.build()
)
.toList();
Comment on lines +47 to +53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

entity.getRole()null일 경우 NPE가 발생할 수 있습니다.

Line 50의 entity.getRole().getRole()rolenull이면 NullPointerException을 발생시킵니다. 현재 findAllByGroupId에서 join fetch를 사용하고 있지만, 이 mapper가 다른 경로에서 호출될 경우를 대비해 방어 코드를 추가하거나, role이 항상 not-null임을 전제로 한다면 해당 전제를 문서화하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java`
around lines 47 - 53, The mapping in GroupMemberMapper that builds MemberInfo
uses entity.getRole().getRole() which can NPE if Role is null; update the map in
GroupMemberMapper (the stream mapping that calls MemberInfo.builder()) to
defensively handle a null role—either by checking entity.getRole() and using a
default (e.g., an Optional.empty/unknown role) or by skipping/throwing a clearer
exception—and ensure any behaviour aligns with callers like findAllByGroupId;
alternatively, document the not-null invariant for Role if you choose not to add
runtime guarding.

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package flipnote.group.api.dto.response;

import java.util.List;

import flipnote.group.application.port.in.result.FindGroupMemberResult;
import flipnote.group.domain.model.member.MemberInfo;

public record FindGroupMemberResponseDto(
List<MemberInfo> memberInfoList
) {

public static FindGroupMemberResponseDto from(FindGroupMemberResult result) {
return new FindGroupMemberResponseDto(result.memberInfoList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package flipnote.group.application.port.in;

import flipnote.group.application.port.in.command.FindGroupMemberCommand;
import flipnote.group.application.port.in.result.FindGroupMemberResult;

public interface FindGroupMemberUseCase {
FindGroupMemberResult findGroupMember(FindGroupMemberCommand cmd);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package flipnote.group.application.port.in.command;

public record FindGroupMemberCommand(
Long userId,
Long groupId
) {
}
Loading