Skip to content

Commit 66d49bf

Browse files
Feat: [FN-307] 그룹 내 유저 조회 API 추가
Feat: [FN-307] 그룹 내 유저 조회 API 추가
2 parents 8dd91b7 + e5fe95c commit 66d49bf

39 files changed

Lines changed: 692 additions & 381 deletions

.github/workflows/cd.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CD
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Login to DockerHub
17+
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
18+
19+
- name: Extract Docker image metadata
20+
id: meta
21+
uses: docker/metadata-action@v5
22+
with:
23+
images: dungbik/flipnote-group
24+
- name: Build and push Docker image
25+
uses: docker/build-push-action@v6
26+
with:
27+
context: .
28+
push: true
29+
tags: ${{ steps.meta.outputs.tags }}
30+
labels: ${{ steps.meta.outputs.labels }}

build.gradle

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ plugins {
22
id 'java'
33
id 'org.springframework.boot' version '4.0.1'
44
id 'io.spring.dependency-management' version '1.1.7'
5-
65
id 'com.google.protobuf' version '0.9.5'
76
}
87

@@ -41,15 +40,17 @@ dependencies {
4140
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
4241
implementation 'org.springframework.boot:spring-boot-starter-validation'
4342

44-
45-
//Spring gRPC Starter
43+
// Spring gRPC Starter
4644
implementation 'org.springframework.grpc:spring-grpc-spring-boot-starter'
4745
implementation 'com.google.protobuf:protobuf-java'
4846
implementation 'io.grpc:grpc-stub'
47+
implementation 'io.grpc:grpc-protobuf:1.62.2'
4948

5049
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
5150

5251
compileOnly 'org.projectlombok:lombok'
52+
compileOnly 'jakarta.annotation:jakarta.annotation-api:2.1.1'
53+
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
5354
developmentOnly 'org.springframework.boot:spring-boot-devtools'
5455
runtimeOnly 'com.mysql:mysql-connector-j'
5556
annotationProcessor 'org.projectlombok:lombok'
@@ -58,52 +59,65 @@ dependencies {
5859
testImplementation 'org.springframework.boot:spring-boot-starter-data-redis-test'
5960
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
6061
testRuntimeOnly 'com.h2database:h2'
62+
6163
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
6264
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
6365
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
6466
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
6567
}
6668

67-
def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile
68-
69-
tasks.named('compileJava', JavaCompile) {
70-
options.generatedSourceOutputDirectory.set(querydslDir)
71-
}
72-
7369
/**
7470
* proto 컴파일 설정
7571
* - .proto 위치: src/main/proto
7672
* - 생성 위치: build/generated/...
7773
*/
7874
protobuf {
79-
protoc {
80-
artifact = "com.google.protobuf:protoc:4.33.2"
81-
}
75+
protoc { artifact = "com.google.protobuf:protoc:3.25.3" }
8276
plugins {
8377
grpc {
84-
artifact = "io.grpc:protoc-gen-grpc-java:1.77.1"
78+
def osName = System.getProperty("os.name").toLowerCase()
79+
def osArch = System.getProperty("os.arch").toLowerCase()
80+
81+
if (osName.contains("win")) {
82+
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2:windows-x86_64@exe"
83+
} else if (osName.contains("mac")) {
84+
if (osArch.contains("aarch64")) {
85+
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2:osx-aarch_64@exe"
86+
} else {
87+
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2:osx-x86_64@exe"
88+
}
89+
} else {
90+
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2:linux-x86_64@exe"
91+
}
8592
}
8693
}
8794
generateProtoTasks {
88-
all().each { task ->
95+
all().configureEach { task ->
8996
task.plugins {
9097
grpc {}
9198
}
9299
}
93100
}
94101
}
95102

103+
def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile
104+
96105
sourceSets {
97106
main {
107+
proto { srcDir "src/main/proto" }
98108
java {
99-
srcDirs += [
109+
srcDirs querydslDir,
100110
"$buildDir/generated/sources/proto/main/java",
101111
"$buildDir/generated/sources/proto/main/grpc"
102-
]
103112
}
104113
}
105114
}
106115

107-
tasks.named('test') {
108-
useJUnitPlatform()
116+
tasks.named('compileJava', JavaCompile) {
117+
options.generatedSourceOutputDirectory.set(querydslDir)
118+
dependsOn tasks.named('generateProto')
119+
}
120+
121+
tasks.named('processResources') {
122+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
109123
}

src/main/java/flipnote/group/adapter/in/web/GroupController.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import flipnote.group.api.dto.request.CreateGroupRequestDto;
1616
import flipnote.group.api.dto.response.ChangeGroupResponseDto;
1717
import flipnote.group.api.dto.response.CreateGroupResponseDto;
18+
import flipnote.group.api.dto.response.FindGroupMemberResponseDto;
1819
import flipnote.group.api.dto.response.FindGroupResponseDto;
1920
import flipnote.group.application.port.in.ChangeGroupUseCase;
2021
import flipnote.group.application.port.in.CreateGroupUseCase;
@@ -24,6 +25,7 @@
2425
import flipnote.group.application.port.in.command.CreateGroupCommand;
2526
import flipnote.group.application.port.in.command.DeleteGroupCommand;
2627
import flipnote.group.application.port.in.command.FindGroupCommand;
28+
import flipnote.group.application.port.in.command.FindGroupMemberCommand;
2729
import jakarta.validation.Valid;
2830
import lombok.RequiredArgsConstructor;
2931

@@ -125,24 +127,20 @@ public ResponseEntity<FindGroupResponseDto> findGroup(
125127
@DeleteMapping("/{groupId}")
126128
public ResponseEntity<Void> deleteGroup(
127129
@RequestHeader("X-USER-ID") Long userId,
128-
@PathVariable("groupId") Long groupId
129-
) {
130+
@PathVariable("groupId") Long groupId) {
130131

131132
DeleteGroupCommand cmd = new DeleteGroupCommand(userId, groupId);
132133

133134
deleteGroupUseCase.deleteGroup(cmd);
134135

135136
return ResponseEntity.noContent().build();
136137
}
137-
138-
//todo 그룹 내 멤버 조회
139138

140139
//todo 그룹 전체 조회
141140

142141
//todo 내 그룹 전체 조회
143142

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

146-
//todo 하위 권한 수정
147145

148146
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package flipnote.group.adapter.in.web;
2+
3+
public class InvitationController {
4+
//todo 가인 신청 요청
5+
6+
//todo 그룹 내 가입 신청한 리스트 조회
7+
8+
//todo 가입신청 응답
9+
10+
//todo 가입신청 삭제
11+
12+
//todo 내가 신청한 가입신청 리스트 조회
13+
14+
//todo 초대
15+
16+
//todo 그룹 멤버 추방
17+
18+
}
Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
11
package flipnote.group.adapter.in.web;
22

3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.PathVariable;
6+
import org.springframework.web.bind.annotation.RequestHeader;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
import flipnote.group.api.dto.response.FindGroupMemberResponseDto;
11+
import flipnote.group.application.port.in.FindGroupMemberUseCase;
12+
import flipnote.group.application.port.in.command.FindGroupMemberCommand;
13+
import flipnote.group.application.port.in.result.FindGroupMemberResult;
14+
import lombok.RequiredArgsConstructor;
15+
16+
@RequiredArgsConstructor
17+
@RestController
18+
@RequestMapping("/v1/groups/{groupId}")
319
public class MemberController {
4-
//todo 가인 신청 요청
5-
6-
//todo 그룹 내 가입 신청한 리스트 조회
7-
8-
//todo 가입신청 응답
9-
10-
//todo 가입신청 삭제
11-
12-
//todo 내가 신청한 가입신청 리스트 조회
1320

14-
//todo 초대
21+
private final FindGroupMemberUseCase findGroupMemberUseCase;
22+
23+
/**
24+
* 그룹 내 멤버 전체 조회
25+
* @param userId
26+
* @param groupId
27+
* @return
28+
*/
29+
@GetMapping("/members")
30+
public ResponseEntity<FindGroupMemberResponseDto> findGroupMembers(
31+
@RequestHeader("X-USER-ID") Long userId,
32+
@PathVariable("groupId") Long groupId) {
33+
34+
FindGroupMemberCommand cmd = new FindGroupMemberCommand(userId, groupId);
35+
36+
FindGroupMemberResult result = findGroupMemberUseCase.findGroupMember(cmd);
37+
38+
FindGroupMemberResponseDto res = FindGroupMemberResponseDto.from(result);
1539

16-
//todo 그룹 멤버 추방
40+
return ResponseEntity.ok(res);
41+
}
1742

43+
//todo 하위 권한 수정
1844
}

src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,56 @@ public void change(
8383
this.maxMember = cmd.maxMember();
8484
this.imageRefId = cmd.imageRefId();
8585
}
86+
87+
/**
88+
* 신규로 그룹 생성
89+
* @param cmd
90+
* @return
91+
*/
92+
public static GroupEntity create(CreateGroupCommand cmd) {
93+
validate(cmd);
94+
95+
return GroupEntity.builder()
96+
.name(cmd.name())
97+
.category(cmd.category())
98+
.description(cmd.description())
99+
.joinPolicy(cmd.joinPolicy())
100+
.visibility(cmd.visibility())
101+
.maxMember(cmd.maxMember())
102+
.imageRefId(cmd.imageRefId())
103+
.memberCount(0)
104+
.build();
105+
}
106+
107+
/**
108+
* 파라미터 검증
109+
* @param cmd
110+
*/
111+
private static void validate(CreateGroupCommand cmd) {
112+
if (cmd == null) {
113+
throw new IllegalArgumentException("command required");
114+
}
115+
116+
if (cmd.name() == null || cmd.name().isBlank()) {
117+
throw new IllegalArgumentException("name required");
118+
}
119+
if (cmd.maxMember() < 1 || cmd.maxMember() > 100) {
120+
throw new IllegalArgumentException("maxMember invalid");
121+
}
122+
if (cmd.category() == null) {
123+
throw new IllegalArgumentException("category required");
124+
}
125+
if (cmd.joinPolicy() == null) {
126+
throw new IllegalArgumentException("join required");
127+
}
128+
if (cmd.visibility() == null) {
129+
throw new IllegalArgumentException("visibility required");
130+
}
131+
if (cmd.description() == null || cmd.description().isBlank()) {
132+
throw new IllegalArgumentException("description required");
133+
}
134+
if (cmd.name().length() > 50) {
135+
throw new IllegalArgumentException("name too long");
136+
}
137+
}
86138
}

src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22

33
import flipnote.group.domain.model.BaseEntity;
44
import flipnote.group.domain.model.member.GroupMemberRole;
5+
import flipnote.group.domain.model.member.MemberInfo;
56
import jakarta.persistence.Column;
67
import jakarta.persistence.Entity;
78
import jakarta.persistence.EnumType;
89
import jakarta.persistence.Enumerated;
10+
import jakarta.persistence.FetchType;
911
import jakarta.persistence.GeneratedValue;
1012
import jakarta.persistence.GenerationType;
1113
import jakarta.persistence.Id;
14+
import jakarta.persistence.JoinColumn;
15+
import jakarta.persistence.ManyToOne;
1216
import jakarta.persistence.Table;
1317
import jakarta.persistence.UniqueConstraint;
1418
import lombok.AccessLevel;
@@ -40,14 +44,15 @@ public class GroupMemberEntity extends BaseEntity {
4044
@Column(name = "user_id", nullable = false)
4145
private Long userId;
4246

43-
@Column(name = "group_role_id", nullable = false)
44-
private Long groupRoleId;
47+
@ManyToOne(fetch = FetchType.LAZY)
48+
@JoinColumn(name = "group_role_id", nullable = false)
49+
private RoleEntity role;
4550

4651
@Builder
47-
private GroupMemberEntity(Long groupId, Long userId, Long groupRoleId) {
52+
private GroupMemberEntity(Long groupId, Long userId, RoleEntity role) {
4853
this.groupId = groupId;
4954
this.userId = userId;
50-
this.groupRoleId = groupRoleId;
55+
this.role = role;
5156
}
5257

5358
/**
@@ -56,11 +61,18 @@ private GroupMemberEntity(Long groupId, Long userId, Long groupRoleId) {
5661
* @param userId
5762
* @return
5863
*/
59-
public static GroupMemberEntity create(Long groupId, Long userId, Long groupRoleId) {
64+
public static GroupMemberEntity create(Long groupId, Long userId, RoleEntity role) {
6065
return GroupMemberEntity.builder()
6166
.groupId(groupId)
6267
.userId(userId)
63-
.groupRoleId(groupRoleId)
68+
.role(role)
69+
.build();
70+
}
71+
72+
public MemberInfo toMemberInfo() {
73+
return MemberInfo.builder()
74+
.userId(this.getUserId())
75+
.role(this.getRole().getRole())
6476
.build();
6577
}
6678
}

0 commit comments

Comments
 (0)