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 @@ -143,4 +143,6 @@ public ResponseEntity<Void> deleteGroup(

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

//todo 하위 권한 수정

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,42 +40,27 @@ public class GroupMemberEntity extends BaseEntity {
@Column(name = "user_id", nullable = false)
private Long userId;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private GroupMemberRole role;
@Column(name = "group_role_id", nullable = false)
private Long groupRoleId;

@Builder
private GroupMemberEntity(Long groupId, Long userId, GroupMemberRole role) {
private GroupMemberEntity(Long groupId, Long userId, Long groupRoleId) {
this.groupId = groupId;
this.userId = userId;
this.role = (role != null) ? role : GroupMemberRole.MEMBER;
this.groupRoleId = groupRoleId;
}

/**
* 오너인 경우
* 멤버 생성
* @param groupId
* @param userId
* @return
*/
public static GroupMemberEntity createOwner(Long groupId, Long userId) {
public static GroupMemberEntity create(Long groupId, Long userId, Long groupRoleId) {
return GroupMemberEntity.builder()
.groupId(groupId)
.userId(userId)
.role(GroupMemberRole.OWNER)
.build();
}

/**
* 오너가 아닌 경우
* @param groupId
* @param userId
* @return
*/
public static GroupMemberEntity join(Long groupId, Long userId) {
return GroupMemberEntity.builder()
.groupId(groupId)
.userId(userId)
.role(GroupMemberRole.MEMBER)
.groupRoleId(groupRoleId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package flipnote.group.adapter.out.entity;

import flipnote.group.domain.model.permission.GroupPermission;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "group_role_permissions",
uniqueConstraints = @UniqueConstraint(
name = "uk_grp_role_perm",
columnNames = {"group_role_id", "permission"}
)
)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PermissionEntity {

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "group_role_id", nullable = false)
private Long groupRoleId;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 50)
private GroupPermission permission;

@Builder
private PermissionEntity(Long groupRoleId, GroupPermission permission) {
this.groupRoleId = groupRoleId;
this.permission = permission;
}

public static PermissionEntity create(Long groupRoleId, GroupPermission permission) {
return PermissionEntity.builder()
.groupRoleId(groupRoleId)
.permission(permission)
.build();
}
}
53 changes: 53 additions & 0 deletions src/main/java/flipnote/group/adapter/out/entity/RoleEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package flipnote.group.adapter.out.entity;

import flipnote.group.domain.model.member.GroupMemberRole;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "group_roles",
uniqueConstraints = @UniqueConstraint(
name = "uk_group_roles_group_name",
columnNames = {"group_id", "role"}
)
)
Comment on lines +19 to +24
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

유니크 제약 조건의 컬럼 이름이 실제 컬럼 이름과 불일치합니다.

Line 22의 columnNames = {"group_id", "role"}에서 "role"을 참조하지만, Line 37의 @Column(name = "group_role")에 의해 실제 DB 컬럼 이름은 "group_role"입니다. DDL 생성 시 오류가 발생하거나 제약 조건이 의도대로 동작하지 않습니다.

또한 제약 조건 이름 uk_group_roles_group_name에서 group_name은 실제 제약 대상과 관련이 없어 혼동됩니다.

🐛 수정 제안
 `@Table`(name = "group_roles",
 	uniqueConstraints = `@UniqueConstraint`(
-		name = "uk_group_roles_group_name",
-		columnNames = {"group_id", "role"}
+		name = "uk_group_roles_group_role",
+		columnNames = {"group_id", "group_role"}
 	)
 )

Also applies to: 36-38

🤖 Prompt for AI Agents
In `@src/main/java/flipnote/group/adapter/out/entity/RoleEntity.java` around lines
19 - 24, RoleEntity에 선언된 `@Table의` uniqueConstraints에서 columnNames에 잘못된 컬럼명을 사용하고
제약 이름이 혼동을 일으킵니다; update the uniqueConstraints in the `@Table` annotation to use
the actual DB column name "group_role" instead of "role" and rename the
constraint from "uk_group_roles_group_name" to a clearer name like
"uk_group_roles_group_role" (or similar). Locate the `@Table`(...) on the
RoleEntity class and the `@Column`(name = "group_role") declaration to ensure the
columnNames array matches "group_role" and the constraint name reflects the
columns it covers.

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RoleEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "group_id", nullable = false)
private Long groupId;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 50, name = "group_role")
private GroupMemberRole role;

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

public static RoleEntity create(Long groupId, GroupMemberRole groupMemberRole) {
return RoleEntity.builder()
.groupId(groupId)
.role(groupMemberRole)
.build();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@ public class GroupMemberRepositoryAdapter implements GroupMemberRepositoryPort {
private final GroupMemberRepository groupMemberRepository;

/**
* 오너일 경우
* 그룹 멤버 저장
* @param groupId
* @param userId
*/
@Override
public void saveOwner(Long groupId, Long userId) {
groupMemberRepository.save(GroupMemberMapper.createOwner(groupId, userId));
public void save(Long groupId, Long userId, Long roleId) {
groupMemberRepository.save(GroupMemberMapper.create(groupId, userId, roleId));
}

/**
* 유저가 그룹 내에 있는지 체크
* @param groupId
* @param userId
* @return
*/
@Override
public boolean existsUserInGroup(Long groupId, Long userId) {
return groupMemberRepository.existsByGroupIdAndUserId(groupId, userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package flipnote.group.adapter.out.persistence;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Repository;

import flipnote.group.adapter.out.entity.PermissionEntity;
import flipnote.group.adapter.out.entity.RoleEntity;
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.GroupRolePermissionRepository;
import flipnote.group.infrastructure.persistence.jpa.GroupRoleRepository;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class GroupRoleRepositoryAdapter implements GroupRoleRepositoryPort {

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

private static final Map<GroupMemberRole, List<GroupPermission>> DEFAULT_PERMS_BY_ROLE =
Map.of(
GroupMemberRole.OWNER, List.of(
GroupPermission.KICK,
GroupPermission.JOIN_REQUEST_MANAGE
),
GroupMemberRole.HEAD_MANAGER, List.of(
GroupPermission.KICK,
GroupPermission.JOIN_REQUEST_MANAGE
),
GroupMemberRole.MANAGER, List.of(
GroupPermission.KICK,
GroupPermission.JOIN_REQUEST_MANAGE
),
GroupMemberRole.MEMBER, List.of()
);

/**
* 그룹 생성시 역할도 추가
* @param groupId
* @return
*/
@Override
public Long create(Long groupId) {
// 오너 역할 생성
Map<GroupMemberRole, Long> roleIdByRole = Arrays.stream(new GroupMemberRole[]{
GroupMemberRole.OWNER,
GroupMemberRole.HEAD_MANAGER,
GroupMemberRole.MANAGER,
GroupMemberRole.MEMBER
})
.collect(java.util.stream.Collectors.toMap(
role -> role,
role -> groupRoleRepository.save(RoleEntity.create(groupId, role)).getId()
));

// 역할별 기본 권한 세팅 (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))
)
.toList();

groupRolePermissionRepository.saveAll(perms);

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

/**
* 해당 유저가 그룹 내에 역할인지 확인
* 오너 여부에서 사용
* @param userId
* @param groupId
* @param groupMemberRole
* @return
*/
@Override
public boolean checkRole(Long userId, Long groupId, GroupMemberRole groupMemberRole) {
RoleEntity roleEntity = groupRoleRepository.findByGroupIdAndRole(groupId, groupMemberRole);

return groupMemberRepository.existsByUserIdAndGroupRoleId(userId, roleEntity.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
@Component
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GroupMemberMapper {
public static GroupMemberEntity createOwner(Long groupId, Long userId) {
return GroupMemberEntity.builder()
.groupId(groupId)
.userId(userId)
.role(GroupMemberRole.OWNER)
.build();
public static GroupMemberEntity create(Long groupId, Long userId, Long roleId) {
return GroupMemberEntity.create(groupId, userId, roleId);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package flipnote.group.application.port.out;

public interface GroupMemberRepositoryPort {
void saveOwner(Long groupId, Long userId);
void save(Long groupId, Long userId, Long roleId);

boolean existsUserInGroup(Long groupId, Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package flipnote.group.application.port.out;

import flipnote.group.domain.model.member.GroupMemberRole;

public interface GroupRoleRepositoryPort {
Long create(Long groupId);

boolean checkRole(Long userId, Long groupId, GroupMemberRole groupMemberRole);
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package flipnote.group.application.service;

import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import flipnote.group.adapter.out.entity.GroupEntity;
import flipnote.group.adapter.out.persistence.GroupRoleRepositoryAdapter;
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate and read the ChangeGroupService.java file
find . -name "ChangeGroupService.java" -type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Find GroupRoleRepositoryPort and GroupRoleRepositoryAdapter files
find . -name "GroupRoleRepository*.java" -type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 325


🏁 Script executed:

#!/bin/bash
# Look for the application service directory structure
find . -path "*/flipnote/group/application/service/*" -name "*.java" -type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 368


🏁 Script executed:

#!/bin/bash
# Read ChangeGroupService.java to see imports and field declarations
head -30 ./src/main/java/flipnote/group/application/service/ChangeGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1138


🏁 Script executed:

#!/bin/bash
# Read GroupRoleRepositoryPort to verify the interface and methods
cat -n ./src/main/java/flipnote/group/application/port/out/GroupRoleRepositoryPort.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 393


🏁 Script executed:

#!/bin/bash
# Read CreateGroupService to compare the correct pattern
head -30 ./src/main/java/flipnote/group/application/service/CreateGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1134


🏁 Script executed:

#!/bin/bash
# Check for all adapter imports in the service layer
rg "import flipnote.group.adapter" ./src/main/java/flipnote/group/application/service/ -n

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 908


Application 서비스 계층에서 포트 인터페이스 대신 구체적인 어댑터를 직접 의존하고 있습니다.

ChangeGroupServiceGroupRoleRepositoryAdapter(구현체)를 직접 참조하고 있어 헥사고날 아키텍처의 의존성 역전 원칙을 위반합니다. 동일한 기능을 수행하는 CreateGroupServiceGroupRoleRepositoryPort 포트 인터페이스를 올바르게 사용하고 있습니다.

다음과 같이 변경하십시오:

♻️ 포트 인터페이스 사용으로 변경
-import flipnote.group.adapter.out.persistence.GroupRoleRepositoryAdapter;
+import flipnote.group.application.port.out.GroupRoleRepositoryPort;
-	private final GroupRoleRepositoryAdapter groupRoleRepository;
+	private final GroupRoleRepositoryPort groupRoleRepository;
🤖 Prompt for AI Agents
In `@src/main/java/flipnote/group/application/service/ChangeGroupService.java` at
line 7, ChangeGroupService currently depends on the concrete
GroupRoleRepositoryAdapter which violates dependency inversion; replace that
concrete dependency with the port interface GroupRoleRepositoryPort by updating
the import, field type, constructor parameter (and any usages) from
GroupRoleRepositoryAdapter to GroupRoleRepositoryPort so the service depends on
the interface (keep the adapter only wired in at composition/root). Ensure all
references inside ChangeGroupService (fields, method signatures, and any casts)
use GroupRoleRepositoryPort and remove direct adapter imports.

import flipnote.group.adapter.out.persistence.mapper.GroupMapper;
import flipnote.group.application.port.in.ChangeGroupUseCase;
import flipnote.group.application.port.in.command.ChangeGroupCommand;
import flipnote.group.application.port.in.result.ChangeGroupResult;
import flipnote.group.domain.model.member.GroupMemberRole;
import flipnote.group.infrastructure.persistence.jpa.GroupRepository;
import lombok.RequiredArgsConstructor;

Expand All @@ -18,6 +18,7 @@
public class ChangeGroupService implements ChangeGroupUseCase {

private final GroupRepository jpaGroupRepository;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check GroupRepository usage in service layer files
rg "GroupRepository" --type=java -g '*/application/service/*' -n

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 219


🏁 Script executed:

#!/bin/bash
# Find and examine ChangeGroupService
fd "ChangeGroupService.java" --type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 143


🏁 Script executed:

#!/bin/bash
# Find GroupRepositoryPort to verify the abstraction exists
fd "GroupRepositoryPort" --type f

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Check the structure of application/service directory
fd . --type f -g "*/application/service/*.java" | head -20

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 186


🏁 Script executed:

#!/bin/bash
# List service files
ls -la src/main/java/flipnote/group/application/service/

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 472


🏁 Script executed:

#!/bin/bash
# Examine ChangeGroupService
cat -n src/main/java/flipnote/group/application/service/ChangeGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1893


🏁 Script executed:

#!/bin/bash
# Examine GroupRepositoryPort
cat -n src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 376


🏁 Script executed:

#!/bin/bash
# Check FindGroupService
cat -n src/main/java/flipnote/group/application/service/FindGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1667


🏁 Script executed:

#!/bin/bash
# Check CreateGroupService to verify the architectural pattern
cat -n src/main/java/flipnote/group/application/service/CreateGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1865


🏁 Script executed:

#!/bin/bash
# Check DeleteGroupService as well
cat -n src/main/java/flipnote/group/application/service/DeleteGroupService.java

Repository: FlipNoteTeam/FlipNote-Group

Length of output: 1385


GroupRepositoryPort를 통해 접근하도록 리팩토링이 필요합니다.

ChangeGroupService가 JPA 인터페이스인 GroupRepository를 직접 참조하고 있는데, FindGroupService, CreateGroupService, DeleteGroupService에서는 모두 GroupRepositoryPort를 통해 접근하고 있습니다. 아키텍처 일관성을 위해 동일한 패턴으로 통일해야 합니다.

🤖 Prompt for AI Agents
In `@src/main/java/flipnote/group/application/service/ChangeGroupService.java` at
line 20, ChangeGroupService currently depends on the JPA interface field
jpaGroupRepository (type GroupRepository); refactor it to depend on the
abstraction GroupRepositoryPort instead: replace the field jpaGroupRepository
with a GroupRepositoryPort-typed field, update the constructor to accept
GroupRepositoryPort, and use that port in all methods of ChangeGroupService
(same access pattern as FindGroupService/CreateGroupService/DeleteGroupService)
so the service no longer directly references GroupRepository.

private final GroupRoleRepositoryAdapter groupRoleRepository;

/**
* 그룹 수정
Expand All @@ -32,6 +33,13 @@ public ChangeGroupResult change(ChangeGroupCommand cmd) {
() -> new IllegalArgumentException("group not Exists")
);

//오너 인지 확인
boolean isOwner = groupRoleRepository.checkRole(cmd.userId(), entity.getId(), GroupMemberRole.OWNER);

if(!isOwner) {
throw new IllegalArgumentException("not owner");
}

entity.change(cmd);

return new ChangeGroupResult(GroupMapper.toDomain(entity));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import flipnote.group.application.port.in.result.CreateGroupResult;
import flipnote.group.application.port.out.GroupMemberRepositoryPort;
import flipnote.group.application.port.out.GroupRepositoryPort;
import flipnote.group.application.port.out.GroupRoleRepositoryPort;
import flipnote.group.domain.model.group.Group;
import lombok.RequiredArgsConstructor;

Expand All @@ -18,6 +19,7 @@ public class CreateGroupService implements CreateGroupUseCase {

private final GroupRepositoryPort groupRepository;
private final GroupMemberRepositoryPort groupMemberRepository;
private final GroupRoleRepositoryPort groupRoleRepository;

/**
* 그룹 생성
Expand All @@ -33,9 +35,12 @@ public CreateGroupResult create(CreateGroupCommand cmd) {

//그룹 도메인 -> 엔티티 변환 후 저장
Long groupId = groupRepository.saveNewGroup(domainGroup);

//그룹 역할 생성
Long roleId = groupRoleRepository.create(groupId);

//그룹 멤버 저장
groupMemberRepository.saveOwner(groupId, cmd.userId());
//생성자 오너 역할로 저장
groupMemberRepository.save(groupId, cmd.userId(), roleId);

return new CreateGroupResult(groupId);
}
Expand Down
Loading