From 97b8cb6c75f644c9ffb41c02e9d691a49373327d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 4 Feb 2026 00:59:50 +0900 Subject: [PATCH 01/10] =?UTF-8?q?Feat:=20=EA=B7=B8=EB=A3=B9=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 74 ++++++++++++++++- .../group/adapter/in/web/GroupController.java | 40 +++++++++ .../group/adapter/out/entity/GroupEntity.java | 69 ++++++++++++++++ .../adapter/out/entity/GroupMemberEntity.java | 82 +++++++++++++++++++ .../dto/request/CreateGroupRequestDto.java | 4 + .../flipnote/group/api/dto/response/.gitkeep | 0 .../dto/response/CreateGroupResponseDto.java | 4 + .../flipnote/group/application/in/.gitkeep | 0 .../flipnote/group/application/out/.gitkeep | 0 .../port/in/CreateGroupUseCase.java | 5 ++ .../port/in/command/CreateGroupCommand.java | 15 ++++ .../port/in/result}/.gitkeep | 0 .../group/application/service/.gitkeep | 0 .../service/CreateGroupService.java | 9 ++ .../group/domain/model/BaseEntity.java | 45 ++++++++++ .../group/domain/model/group/.gitkeep | 0 .../group/domain/model/group/Category.java | 16 ++++ .../group/domain/model/group/Group.java | 47 +++++++++++ .../group/domain/model/group/GroupId.java | 4 + .../group/domain/model/group/JoinPolicy.java | 6 ++ .../group/domain/model/member/.gitkeep | 0 .../domain/model/member/GroupMember.java | 47 +++++++++++ .../group/domain/model/member/GroupRole.java | 5 ++ .../group/domain/model/user/UserId.java | 4 + 24 files changed, 473 insertions(+), 3 deletions(-) create mode 100644 src/main/java/flipnote/group/adapter/in/web/GroupController.java create mode 100644 src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java create mode 100644 src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java create mode 100644 src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java delete mode 100644 src/main/java/flipnote/group/api/dto/response/.gitkeep create mode 100644 src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java delete mode 100644 src/main/java/flipnote/group/application/in/.gitkeep delete mode 100644 src/main/java/flipnote/group/application/out/.gitkeep create mode 100644 src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java create mode 100644 src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java rename src/main/java/flipnote/group/{adapter/in/web => application/port/in/result}/.gitkeep (100%) delete mode 100644 src/main/java/flipnote/group/application/service/.gitkeep create mode 100644 src/main/java/flipnote/group/application/service/CreateGroupService.java delete mode 100644 src/main/java/flipnote/group/domain/model/group/.gitkeep create mode 100644 src/main/java/flipnote/group/domain/model/group/Category.java create mode 100644 src/main/java/flipnote/group/domain/model/group/Group.java create mode 100644 src/main/java/flipnote/group/domain/model/group/GroupId.java create mode 100644 src/main/java/flipnote/group/domain/model/group/JoinPolicy.java delete mode 100644 src/main/java/flipnote/group/domain/model/member/.gitkeep create mode 100644 src/main/java/flipnote/group/domain/model/member/GroupMember.java create mode 100644 src/main/java/flipnote/group/domain/model/member/GroupRole.java create mode 100644 src/main/java/flipnote/group/domain/model/user/UserId.java diff --git a/build.gradle b/build.gradle index dc48c9c..43ffcf3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,17 @@ plugins { id 'java' id 'org.springframework.boot' version '4.0.1' id 'io.spring.dependency-management' version '1.1.7' + + id 'com.google.protobuf' version '0.9.5' +} + +ext { + springGrpcVersion = "1.0.1" } group = 'flipnote' version = '0.0.1-SNAPSHOT' -description = 'Group' +description = 'Image' java { toolchain { @@ -24,16 +30,78 @@ repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom "org.springframework.grpc:spring-grpc-dependencies:$springGrpcVersion" + } +} + dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + + + //Spring gRPC Starter + implementation 'org.springframework.grpc:spring-grpc-spring-boot-starter' + implementation 'com.google.protobuf:protobuf-java' + implementation 'io.grpc:grpc-stub' + + implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation 'org.springframework.boot:spring-boot-starter-webmvc' + compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' - testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + + testImplementation 'org.springframework.boot:spring-boot-starter-data-redis-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly 'com.h2database:h2' + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" +} + +def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile + +tasks.withType(JavaCompile).configureEach { + options.generatedSourceOutputDirectory.set(querydslDir) +} + +/** + * proto 컴파일 설정 + * - .proto 위치: src/main/proto + * - 생성 위치: build/generated/... + */ +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.25.3" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:1.64.0" + } + } + generateProtoTasks { + all().each { task -> + task.plugins { + grpc {} + } + } + } +} + +sourceSets { + main { + java { + srcDirs += [ + "$buildDir/generated/sources/proto/main/java", + "$buildDir/generated/sources/proto/main/grpc" + ] + } + } } tasks.named('test') { diff --git a/src/main/java/flipnote/group/adapter/in/web/GroupController.java b/src/main/java/flipnote/group/adapter/in/web/GroupController.java new file mode 100644 index 0000000..5cbf9e9 --- /dev/null +++ b/src/main/java/flipnote/group/adapter/in/web/GroupController.java @@ -0,0 +1,40 @@ +package flipnote.group.adapter.in.web; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +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.request.CreateGroupRequestDto; +import flipnote.group.api.dto.response.CreateGroupResponseDto; +import flipnote.group.application.port.in.CreateGroupUseCase; +import flipnote.group.application.port.in.command.CreateGroupCommand; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/v1/groups") +public class GroupController { + + private final CreateGroupUseCase createGroupUseCase; + + /** + * 그룹 생성 API + * @param userId + * @param req + * @return + */ + @PostMapping("") + public ResponseEntity createGroup( + @RequestHeader("X-USER-ID") Long userId, + @RequestBody @Valid CreateGroupRequestDto req) { + + CreateGroupCommand cmd = new CreateGroupCommand(); + + return null; + } + +} diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java new file mode 100644 index 0000000..9d2fd2d --- /dev/null +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java @@ -0,0 +1,69 @@ +package flipnote.group.adapter.out.entity; + +import flipnote.group.application.port.in.command.CreateGroupCommand; +import flipnote.group.domain.model.BaseEntity; +import flipnote.group.domain.model.group.Category; +import flipnote.group.domain.model.group.JoinPolicy; +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 lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "groups") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class GroupEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Category category; + + @Column(nullable = false) + private String description; + + @Column(nullable = false) + private JoinPolicy joinPolicy; + + @Column(nullable = false) + private Boolean visibility; + + @Column(nullable = false) + private Integer maxMember; + + private String imageUrl; + + @Column(nullable = false) + private Integer memberCount; + + private void init(String name, Category category, String description, JoinPolicy joinPolicy, Boolean visibility, + Integer maxMember, String imageUrl, Integer memberCount) { + this.name = name; + this.category = category; + this.description = description; + this.joinPolicy = joinPolicy; + this.visibility = visibility; + this.maxMember = maxMember; + this.imageUrl = imageUrl; + this.memberCount = memberCount; + } + + public void increaseMemberCount() { + this.memberCount++; + } +} diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java new file mode 100644 index 0000000..26367a3 --- /dev/null +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java @@ -0,0 +1,82 @@ +package flipnote.group.adapter.out.entity; + +import flipnote.group.domain.model.group.GroupId; +import flipnote.group.domain.model.member.GroupRole; +import flipnote.group.domain.model.user.UserId; +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; + +@Getter +@Entity +@Table( + name = "group_members", + uniqueConstraints = { + @UniqueConstraint( + name = "uk_group_members_group_user", + columnNames = {"group_id", "user_id"} + ) + } +) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class GroupMemberEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "group_id", nullable = false) + private Long groupId; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private GroupRole role; + + @Builder + private GroupMemberEntity(Long groupId, Long userId, GroupRole role) { + this.groupId = groupId; + this.userId = userId; + this.role = (role != null) ? role : GroupRole.MEMBER; + } + + /** + * 오너인 경우 + * @param groupId + * @param userId + * @return + */ + public static GroupMemberEntity createOwner(Long groupId, Long userId) { + return GroupMemberEntity.builder() + .groupId(groupId) + .userId(userId) + .role(GroupRole.OWNER) + .build(); + } + + /** + * 오너가 아닌 경우 + * @param groupId + * @param userId + * @return + */ + public static GroupMemberEntity join(Long groupId, Long userId) { + return GroupMemberEntity.builder() + .groupId(groupId) + .userId(userId) + .role(GroupRole.MEMBER) + .build(); + } +} diff --git a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java new file mode 100644 index 0000000..ee5b90b --- /dev/null +++ b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java @@ -0,0 +1,4 @@ +package flipnote.group.api.dto.request; + +public class CreateGroupRequestDto { +} diff --git a/src/main/java/flipnote/group/api/dto/response/.gitkeep b/src/main/java/flipnote/group/api/dto/response/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java b/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java new file mode 100644 index 0000000..0352fb1 --- /dev/null +++ b/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java @@ -0,0 +1,4 @@ +package flipnote.group.api.dto.response; + +public class CreateGroupResponseDto { +} diff --git a/src/main/java/flipnote/group/application/in/.gitkeep b/src/main/java/flipnote/group/application/in/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/application/out/.gitkeep b/src/main/java/flipnote/group/application/out/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java b/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java new file mode 100644 index 0000000..65d9adc --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java @@ -0,0 +1,5 @@ +package flipnote.group.application.port.in; + + +public interface CreateGroupUseCase { +} diff --git a/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java b/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java new file mode 100644 index 0000000..e7a9000 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java @@ -0,0 +1,15 @@ +package flipnote.group.application.port.in.command; + +import flipnote.group.domain.model.group.Category; +import flipnote.group.domain.model.group.JoinPolicy; + +public record CreateGroupCommand( + String name, + Category category, + String description, + JoinPolicy joinPolicy, + boolean visibility, + int maxMember, + String imageUrl +) { +} diff --git a/src/main/java/flipnote/group/adapter/in/web/.gitkeep b/src/main/java/flipnote/group/application/port/in/result/.gitkeep similarity index 100% rename from src/main/java/flipnote/group/adapter/in/web/.gitkeep rename to src/main/java/flipnote/group/application/port/in/result/.gitkeep diff --git a/src/main/java/flipnote/group/application/service/.gitkeep b/src/main/java/flipnote/group/application/service/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/application/service/CreateGroupService.java b/src/main/java/flipnote/group/application/service/CreateGroupService.java new file mode 100644 index 0000000..87c08b4 --- /dev/null +++ b/src/main/java/flipnote/group/application/service/CreateGroupService.java @@ -0,0 +1,9 @@ +package flipnote.group.application.service; + +import org.springframework.stereotype.Service; + +import flipnote.group.application.port.in.CreateGroupUseCase; + +@Service +public class CreateGroupService implements CreateGroupUseCase { +} diff --git a/src/main/java/flipnote/group/domain/model/BaseEntity.java b/src/main/java/flipnote/group/domain/model/BaseEntity.java index 6db03b9..bc2fc30 100644 --- a/src/main/java/flipnote/group/domain/model/BaseEntity.java +++ b/src/main/java/flipnote/group/domain/model/BaseEntity.java @@ -1,4 +1,49 @@ package flipnote.group.domain.model; +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@Getter +@MappedSuperclass +@EntityListeners(AutoCloseable.class) public abstract class BaseEntity { + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime modifiedAt; + + private LocalDateTime deletedAt; + + /** + * 삭제 표시 + * 삭제시간 == 현재 시간 + */ + protected void markDeleted() { + this.deletedAt = LocalDateTime.now(); + } + + /** + * 삭제되었는지 확인하는 메서드 + * @return + */ + public boolean isDeleted() { + return this.deletedAt != null; + } + + /** + * 삭제 복구 + */ + protected void restore() { + this.deletedAt = null; + } } diff --git a/src/main/java/flipnote/group/domain/model/group/.gitkeep b/src/main/java/flipnote/group/domain/model/group/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/domain/model/group/Category.java b/src/main/java/flipnote/group/domain/model/group/Category.java new file mode 100644 index 0000000..6b9635d --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/group/Category.java @@ -0,0 +1,16 @@ +package flipnote.group.domain.model.group; + +public enum Category { + IT, ENGLISH, MATH, SCIENCE, HISTORY, GEOGRAPHY, KOREAN; + + public static Category from(String category) { + if (category == null || category.isEmpty()) { + return null; + } + try { + return Category.valueOf(category); + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/flipnote/group/domain/model/group/Group.java b/src/main/java/flipnote/group/domain/model/group/Group.java new file mode 100644 index 0000000..1a9a7f6 --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/group/Group.java @@ -0,0 +1,47 @@ +package flipnote.group.domain.model.group; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Group { + + private GroupId id; + private String name; + private String description; + private int maxMember; + private int memberCount; + + public static Group create(String name, String description, int maxMember) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("group name is blank"); + } + if (maxMember < 1 || maxMember > 100) { + throw new IllegalArgumentException("group is max"); + } + + Group group = new Group(); + group.name = name; + group.description = description; + group.maxMember = maxMember; + + group.memberCount = 1; + return group; + } + + public void validateJoinable() { + if (memberCount >= maxMember) { + throw new IllegalArgumentException("group is max"); + } + } + + public void increaseMemberCount() { + validateJoinable(); + memberCount++; + } +} diff --git a/src/main/java/flipnote/group/domain/model/group/GroupId.java b/src/main/java/flipnote/group/domain/model/group/GroupId.java new file mode 100644 index 0000000..7467641 --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/group/GroupId.java @@ -0,0 +1,4 @@ +package flipnote.group.domain.model.group; + +public record GroupId(Long value) { +} diff --git a/src/main/java/flipnote/group/domain/model/group/JoinPolicy.java b/src/main/java/flipnote/group/domain/model/group/JoinPolicy.java new file mode 100644 index 0000000..5cf2481 --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/group/JoinPolicy.java @@ -0,0 +1,6 @@ +package flipnote.group.domain.model.group; + +public enum JoinPolicy { + OPEN, // 바로 가입 가능 + APPROVAL // 가입 승인 필요 +} diff --git a/src/main/java/flipnote/group/domain/model/member/.gitkeep b/src/main/java/flipnote/group/domain/model/member/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/domain/model/member/GroupMember.java b/src/main/java/flipnote/group/domain/model/member/GroupMember.java new file mode 100644 index 0000000..75f9fdd --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/member/GroupMember.java @@ -0,0 +1,47 @@ +package flipnote.group.domain.model.member; + +import flipnote.group.domain.model.group.GroupId; +import flipnote.group.domain.model.user.UserId; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class GroupMember { + private Long id; + private GroupId groupId; + private UserId userId; + private GroupRole role; + + /** + * 유저가 오너일 경우 + * @param groupId + * @param ownerUserId + * @return + */ + public static GroupMember createOwner(GroupId groupId, UserId ownerUserId) { + GroupMember gm = new GroupMember(); + gm.groupId = groupId; + gm.userId = ownerUserId; + gm.role = GroupRole.OWNER; + return gm; + } + + /** + * 오너가 아닐 경우 + * @param groupId + * @param userId + * @return + */ + public static GroupMember join(GroupId groupId, UserId userId) { + GroupMember gm = new GroupMember(); + gm.groupId = groupId; + gm.userId = userId; + gm.role = GroupRole.MEMBER; + return gm; + } + +} diff --git a/src/main/java/flipnote/group/domain/model/member/GroupRole.java b/src/main/java/flipnote/group/domain/model/member/GroupRole.java new file mode 100644 index 0000000..9506fd7 --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/member/GroupRole.java @@ -0,0 +1,5 @@ +package flipnote.group.domain.model.member; + +public enum GroupRole { + OWNER, HEAD_MANAGER, MANAGER, STAFF, MEMBER +} diff --git a/src/main/java/flipnote/group/domain/model/user/UserId.java b/src/main/java/flipnote/group/domain/model/user/UserId.java new file mode 100644 index 0000000..1545b53 --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/user/UserId.java @@ -0,0 +1,4 @@ +package flipnote.group.domain.model.user; + +public record UserId(Long value) { +} From 474c07fa8c48da4fc00a7676c76506e790f5cce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 4 Feb 2026 18:20:06 +0900 Subject: [PATCH 02/10] =?UTF-8?q?Delete:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/flipnote/group/adapter/out/.gitkeep | 0 src/main/java/flipnote/group/api/dto/request/.gitkeep | 0 src/main/java/flipnote/group/application/port/in/result/.gitkeep | 0 src/main/java/flipnote/group/infrastructure/config/.gitkeep | 0 src/main/java/flipnote/group/infrastructure/persistence/.gitkeep | 0 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/java/flipnote/group/adapter/out/.gitkeep delete mode 100644 src/main/java/flipnote/group/api/dto/request/.gitkeep delete mode 100644 src/main/java/flipnote/group/application/port/in/result/.gitkeep delete mode 100644 src/main/java/flipnote/group/infrastructure/config/.gitkeep delete mode 100644 src/main/java/flipnote/group/infrastructure/persistence/.gitkeep diff --git a/src/main/java/flipnote/group/adapter/out/.gitkeep b/src/main/java/flipnote/group/adapter/out/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/api/dto/request/.gitkeep b/src/main/java/flipnote/group/api/dto/request/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/application/port/in/result/.gitkeep b/src/main/java/flipnote/group/application/port/in/result/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/infrastructure/config/.gitkeep b/src/main/java/flipnote/group/infrastructure/config/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/flipnote/group/infrastructure/persistence/.gitkeep b/src/main/java/flipnote/group/infrastructure/persistence/.gitkeep deleted file mode 100644 index e69de29..0000000 From d0ad202263be95d62fe38f19db5ce31cb7712494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 4 Feb 2026 18:20:34 +0900 Subject: [PATCH 03/10] =?UTF-8?q?Feat:=20=EA=B7=B8=EB=A3=B9=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/adapter/in/web/GroupController.java | 16 ++- .../group/adapter/out/entity/GroupEntity.java | 14 +-- .../adapter/out/entity/GroupMemberEntity.java | 17 ++-- .../GroupMemberRepositoryAdapter.java | 26 +++++ .../persistence/GroupRepositoryAdapter.java | 23 +++++ .../out/persistence/mapper/GroupMapper.java | 31 ++++++ .../persistence/mapper/GroupMemberMapper.java | 20 ++++ .../dto/request/CreateGroupRequestDto.java | 19 +++- .../dto/response/CreateGroupResponseDto.java | 7 +- .../port/in/CreateGroupUseCase.java | 3 + .../port/in/command/CreateGroupCommand.java | 3 +- .../port/in/result/CreateGroupResult.java | 5 + .../port/out/GroupMemberRepositoryPort.java | 7 ++ .../port/out/GroupRepositoryPort.java | 8 ++ .../service/CreateGroupService.java | 34 +++++++ .../group/domain/model/BaseEntity.java | 3 +- .../group/domain/model/group/Group.java | 99 +++++++++++++++---- .../group/domain/model/group/Visibility.java | 5 + .../domain/model/member/GroupMember.java | 6 +- .../{GroupRole.java => GroupMemberRole.java} | 2 +- .../jpa/GroupMemberRepository.java | 8 ++ .../persistence/jpa/GroupRepository.java | 8 ++ 22 files changed, 318 insertions(+), 46 deletions(-) create mode 100644 src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java create mode 100644 src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java create mode 100644 src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java create mode 100644 src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java create mode 100644 src/main/java/flipnote/group/application/port/in/result/CreateGroupResult.java create mode 100644 src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java create mode 100644 src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java create mode 100644 src/main/java/flipnote/group/domain/model/group/Visibility.java rename src/main/java/flipnote/group/domain/model/member/{GroupRole.java => GroupMemberRole.java} (75%) create mode 100644 src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java create mode 100644 src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java diff --git a/src/main/java/flipnote/group/adapter/in/web/GroupController.java b/src/main/java/flipnote/group/adapter/in/web/GroupController.java index 5cbf9e9..f91c424 100644 --- a/src/main/java/flipnote/group/adapter/in/web/GroupController.java +++ b/src/main/java/flipnote/group/adapter/in/web/GroupController.java @@ -32,9 +32,19 @@ public ResponseEntity createGroup( @RequestHeader("X-USER-ID") Long userId, @RequestBody @Valid CreateGroupRequestDto req) { - CreateGroupCommand cmd = new CreateGroupCommand(); - - return null; + CreateGroupCommand cmd = new CreateGroupCommand( + req.name(), + req.category(), + req.description(), + req.joinPolicy(), + req.visibility(), + req.maxMember(), + req.imageUrl() + ); + + var result = createGroupUseCase.create(cmd, userId); + CreateGroupResponseDto res = CreateGroupResponseDto.from(result.groupId()); + return ResponseEntity.ok(res); } } diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java index 9d2fd2d..519a20c 100644 --- a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java @@ -4,6 +4,7 @@ import flipnote.group.domain.model.BaseEntity; import flipnote.group.domain.model.group.Category; import flipnote.group.domain.model.group.JoinPolicy; +import flipnote.group.domain.model.group.Visibility; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -19,7 +20,7 @@ @Getter @Entity -@Table(name = "groups") +@Table(name = "app_groups") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class GroupEntity extends BaseEntity { @@ -37,11 +38,13 @@ public class GroupEntity extends BaseEntity { @Column(nullable = false) private String description; + @Enumerated(EnumType.STRING) @Column(nullable = false) private JoinPolicy joinPolicy; + @Enumerated(EnumType.STRING) @Column(nullable = false) - private Boolean visibility; + private Visibility visibility; @Column(nullable = false) private Integer maxMember; @@ -51,7 +54,8 @@ public class GroupEntity extends BaseEntity { @Column(nullable = false) private Integer memberCount; - private void init(String name, Category category, String description, JoinPolicy joinPolicy, Boolean visibility, + @Builder + private GroupEntity(String name, Category category, String description, JoinPolicy joinPolicy, Visibility visibility, Integer maxMember, String imageUrl, Integer memberCount) { this.name = name; this.category = category; @@ -62,8 +66,4 @@ private void init(String name, Category category, String description, JoinPolicy this.imageUrl = imageUrl; this.memberCount = memberCount; } - - public void increaseMemberCount() { - this.memberCount++; - } } diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java index 26367a3..8161510 100644 --- a/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupMemberEntity.java @@ -1,8 +1,7 @@ package flipnote.group.adapter.out.entity; -import flipnote.group.domain.model.group.GroupId; -import flipnote.group.domain.model.member.GroupRole; -import flipnote.group.domain.model.user.UserId; +import flipnote.group.domain.model.BaseEntity; +import flipnote.group.domain.model.member.GroupMemberRole; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -29,7 +28,7 @@ } ) @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class GroupMemberEntity { +public class GroupMemberEntity extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -43,13 +42,13 @@ public class GroupMemberEntity { @Enumerated(EnumType.STRING) @Column(nullable = false) - private GroupRole role; + private GroupMemberRole role; @Builder - private GroupMemberEntity(Long groupId, Long userId, GroupRole role) { + private GroupMemberEntity(Long groupId, Long userId, GroupMemberRole role) { this.groupId = groupId; this.userId = userId; - this.role = (role != null) ? role : GroupRole.MEMBER; + this.role = (role != null) ? role : GroupMemberRole.MEMBER; } /** @@ -62,7 +61,7 @@ public static GroupMemberEntity createOwner(Long groupId, Long userId) { return GroupMemberEntity.builder() .groupId(groupId) .userId(userId) - .role(GroupRole.OWNER) + .role(GroupMemberRole.OWNER) .build(); } @@ -76,7 +75,7 @@ public static GroupMemberEntity join(Long groupId, Long userId) { return GroupMemberEntity.builder() .groupId(groupId) .userId(userId) - .role(GroupRole.MEMBER) + .role(GroupMemberRole.MEMBER) .build(); } } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java new file mode 100644 index 0000000..ad903b3 --- /dev/null +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java @@ -0,0 +1,26 @@ +package flipnote.group.adapter.out.persistence; + +import org.springframework.stereotype.Repository; + +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 lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +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)); + } +} diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java new file mode 100644 index 0000000..875bc6e --- /dev/null +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java @@ -0,0 +1,23 @@ +package flipnote.group.adapter.out.persistence; + +import org.springframework.stereotype.Repository; + +import flipnote.group.adapter.out.entity.GroupEntity; +import flipnote.group.application.port.out.GroupRepositoryPort; +import flipnote.group.infrastructure.persistence.jpa.GroupRepository; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class GroupRepositoryAdapter implements GroupRepositoryPort { + + private final GroupRepository groupRepository; + + @Override + public Long saveNewGroup(GroupEntity groupEntity) { + + GroupEntity group = groupRepository.save(groupEntity); + + return group.getId(); + } +} diff --git a/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java b/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java new file mode 100644 index 0000000..aa35f4f --- /dev/null +++ b/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java @@ -0,0 +1,31 @@ +package flipnote.group.adapter.out.persistence.mapper; + +import org.springframework.stereotype.Component; + +import flipnote.group.adapter.out.entity.GroupEntity; +import flipnote.group.domain.model.group.Group; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@Component +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class GroupMapper { + + /** + * 새로운 엔티티 생성 + * @param domain + * @return + */ + public static GroupEntity createNewEntity(Group domain) { + return GroupEntity.builder() + .name(domain.getName()) + .category(domain.getCategory()) + .description(domain.getDescription()) + .joinPolicy(domain.getJoinPolicy()) + .visibility(domain.getVisibility()) + .maxMember(domain.getMaxMember()) + .imageUrl(domain.getImageUrl()) + .memberCount(domain.getMemberCount()) + .build(); + } +} diff --git a/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java b/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java new file mode 100644 index 0000000..5d1e4b7 --- /dev/null +++ b/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMemberMapper.java @@ -0,0 +1,20 @@ +package flipnote.group.adapter.out.persistence.mapper; + +import org.springframework.stereotype.Component; + +import flipnote.group.adapter.out.entity.GroupMemberEntity; +import flipnote.group.domain.model.member.GroupMemberRole; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@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(); + } +} diff --git a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java index ee5b90b..4b8cf45 100644 --- a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java +++ b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java @@ -1,4 +1,19 @@ package flipnote.group.api.dto.request; -public class CreateGroupRequestDto { -} +import flipnote.group.domain.model.group.Category; +import flipnote.group.domain.model.group.JoinPolicy; +import flipnote.group.domain.model.group.Visibility; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record CreateGroupRequestDto( + @NotBlank String name, + @NotNull Category category, + @NotBlank String description, + @NotNull JoinPolicy joinPolicy, + @NotNull Visibility visibility, + @NotNull @Min(1) @Max(100) Integer maxMember, + String imageUrl +) {} diff --git a/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java b/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java index 0352fb1..39d2c76 100644 --- a/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java +++ b/src/main/java/flipnote/group/api/dto/response/CreateGroupResponseDto.java @@ -1,4 +1,9 @@ package flipnote.group.api.dto.response; -public class CreateGroupResponseDto { +public record CreateGroupResponseDto( + Long groupId +) { + public static CreateGroupResponseDto from(Long groupId) { + return new CreateGroupResponseDto(groupId); + } } diff --git a/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java b/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java index 65d9adc..d58a135 100644 --- a/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java +++ b/src/main/java/flipnote/group/application/port/in/CreateGroupUseCase.java @@ -1,5 +1,8 @@ package flipnote.group.application.port.in; +import flipnote.group.application.port.in.command.CreateGroupCommand; +import flipnote.group.application.port.in.result.CreateGroupResult; public interface CreateGroupUseCase { + CreateGroupResult create(CreateGroupCommand cmd, Long userId); } diff --git a/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java b/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java index e7a9000..94fab57 100644 --- a/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java +++ b/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java @@ -2,13 +2,14 @@ import flipnote.group.domain.model.group.Category; import flipnote.group.domain.model.group.JoinPolicy; +import flipnote.group.domain.model.group.Visibility; public record CreateGroupCommand( String name, Category category, String description, JoinPolicy joinPolicy, - boolean visibility, + Visibility visibility, int maxMember, String imageUrl ) { diff --git a/src/main/java/flipnote/group/application/port/in/result/CreateGroupResult.java b/src/main/java/flipnote/group/application/port/in/result/CreateGroupResult.java new file mode 100644 index 0000000..277caa7 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/in/result/CreateGroupResult.java @@ -0,0 +1,5 @@ +package flipnote.group.application.port.in.result; + +public record CreateGroupResult( + Long groupId +) {} diff --git a/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java new file mode 100644 index 0000000..8622b41 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/out/GroupMemberRepositoryPort.java @@ -0,0 +1,7 @@ +package flipnote.group.application.port.out; + +import flipnote.group.domain.model.member.GroupMemberRole; + +public interface GroupMemberRepositoryPort { + void saveOwner(Long groupId, Long userId); +} diff --git a/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java new file mode 100644 index 0000000..bc00f90 --- /dev/null +++ b/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java @@ -0,0 +1,8 @@ +package flipnote.group.application.port.out; + +import flipnote.group.adapter.out.entity.GroupEntity; +import flipnote.group.application.port.in.command.CreateGroupCommand; + +public interface GroupRepositoryPort { + Long saveNewGroup(GroupEntity groupEntity); +} diff --git a/src/main/java/flipnote/group/application/service/CreateGroupService.java b/src/main/java/flipnote/group/application/service/CreateGroupService.java index 87c08b4..83b66ea 100644 --- a/src/main/java/flipnote/group/application/service/CreateGroupService.java +++ b/src/main/java/flipnote/group/application/service/CreateGroupService.java @@ -1,9 +1,43 @@ package flipnote.group.application.service; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import flipnote.group.adapter.out.persistence.mapper.GroupMapper; import flipnote.group.application.port.in.CreateGroupUseCase; +import flipnote.group.application.port.in.command.CreateGroupCommand; +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.domain.model.group.Group; +import lombok.RequiredArgsConstructor; @Service +@RequiredArgsConstructor public class CreateGroupService implements CreateGroupUseCase { + + private final GroupRepositoryPort groupRepository; + private final GroupMemberRepositoryPort groupMemberRepository; + + /** + * 그룹 생성 + * @param cmd + * @param userId + * @return + */ + @Override + @Transactional + public CreateGroupResult create(CreateGroupCommand cmd, Long userId) { + + //도메인 생성 및 검증 + var domainGroup = Group.create(cmd); + + //그룹 도메인 -> 엔티티 변환 후 저장 + Long groupId = groupRepository.saveNewGroup(GroupMapper.createNewEntity(domainGroup)); + + //그룹 멤버 저장 + groupMemberRepository.saveOwner(groupId, userId); + + return new CreateGroupResult(groupId); + } } diff --git a/src/main/java/flipnote/group/domain/model/BaseEntity.java b/src/main/java/flipnote/group/domain/model/BaseEntity.java index bc2fc30..5541274 100644 --- a/src/main/java/flipnote/group/domain/model/BaseEntity.java +++ b/src/main/java/flipnote/group/domain/model/BaseEntity.java @@ -4,6 +4,7 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; @@ -12,7 +13,7 @@ @Getter @MappedSuperclass -@EntityListeners(AutoCloseable.class) +@EntityListeners(AuditingEntityListener.class) public abstract class BaseEntity { @CreatedDate diff --git a/src/main/java/flipnote/group/domain/model/group/Group.java b/src/main/java/flipnote/group/domain/model/group/Group.java index 1a9a7f6..85843c6 100644 --- a/src/main/java/flipnote/group/domain/model/group/Group.java +++ b/src/main/java/flipnote/group/domain/model/group/Group.java @@ -1,5 +1,6 @@ package flipnote.group.domain.model.group; +import flipnote.group.application.port.in.command.CreateGroupCommand; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -11,37 +12,99 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class Group { - private GroupId id; + private Long id; + private String name; + private Category category; private String description; + private JoinPolicy joinPolicy; + private Visibility visibility; + private int maxMember; + private String imageUrl; private int memberCount; - public static Group create(String name, String description, int maxMember) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("group name is blank"); - } - if (maxMember < 1 || maxMember > 100) { - throw new IllegalArgumentException("group is max"); - } + /** + * 신규로 그룹 생성 + * @param cmd + * @return + */ + public static Group create(CreateGroupCommand cmd) { + validate(cmd); Group group = new Group(); - group.name = name; - group.description = description; - group.maxMember = maxMember; + group.name = cmd.name(); + group.category = cmd.category(); + group.description = cmd.description(); + group.joinPolicy = cmd.joinPolicy(); + group.visibility = cmd.visibility(); + group.maxMember = cmd.maxMember(); + group.imageUrl = cmd.imageUrl(); group.memberCount = 1; + return group; } - public void validateJoinable() { - if (memberCount >= maxMember) { - throw new IllegalArgumentException("group is max"); - } + /** + * DB 가져오기 + * @param id + * @param name + * @param category + * @param description + * @param joinPolicy + * @param visibility + * @param maxMember + * @param imageUrl + * @param memberCount + * @return + */ + public static Group getGroup( + Long id, + String name, + Category category, + String description, + JoinPolicy joinPolicy, + Visibility visibility, + int maxMember, + String imageUrl, + int memberCount + ) { + Group g = new Group(); + g.id = id; + g.name = name; + g.category = category; + g.description = description; + g.joinPolicy = joinPolicy; + g.visibility = visibility; + g.maxMember = maxMember; + g.imageUrl = imageUrl; + g.memberCount = memberCount; + return g; } - public void increaseMemberCount() { - validateJoinable(); - memberCount++; + /** + * 파라미터 검증 + * @param cmd + */ + private static void validate(CreateGroupCommand cmd) { + if (cmd.name() == null || cmd.name().isBlank()) { + throw new IllegalArgumentException("name required"); + } + if (cmd.maxMember() < 1 || cmd.maxMember() > 100) { + throw new IllegalArgumentException("maxMember invalid"); + } + if (cmd.category() == null) { + throw new IllegalArgumentException("category required"); + } + if (cmd.joinPolicy() == null) { + throw new IllegalArgumentException("join required"); + } + if (cmd.visibility() == null) { + throw new IllegalArgumentException("visibility required"); + } + if (cmd.description() == null || cmd.description().isBlank()) { + throw new IllegalArgumentException("description required"); + } } } diff --git a/src/main/java/flipnote/group/domain/model/group/Visibility.java b/src/main/java/flipnote/group/domain/model/group/Visibility.java new file mode 100644 index 0000000..4d0cfab --- /dev/null +++ b/src/main/java/flipnote/group/domain/model/group/Visibility.java @@ -0,0 +1,5 @@ +package flipnote.group.domain.model.group; + +public enum Visibility { + PUBLIC, PRIVATE +} diff --git a/src/main/java/flipnote/group/domain/model/member/GroupMember.java b/src/main/java/flipnote/group/domain/model/member/GroupMember.java index 75f9fdd..a08555c 100644 --- a/src/main/java/flipnote/group/domain/model/member/GroupMember.java +++ b/src/main/java/flipnote/group/domain/model/member/GroupMember.java @@ -14,7 +14,7 @@ public class GroupMember { private Long id; private GroupId groupId; private UserId userId; - private GroupRole role; + private GroupMemberRole role; /** * 유저가 오너일 경우 @@ -26,7 +26,7 @@ public static GroupMember createOwner(GroupId groupId, UserId ownerUserId) { GroupMember gm = new GroupMember(); gm.groupId = groupId; gm.userId = ownerUserId; - gm.role = GroupRole.OWNER; + gm.role = GroupMemberRole.OWNER; return gm; } @@ -40,7 +40,7 @@ public static GroupMember join(GroupId groupId, UserId userId) { GroupMember gm = new GroupMember(); gm.groupId = groupId; gm.userId = userId; - gm.role = GroupRole.MEMBER; + gm.role = GroupMemberRole.MEMBER; return gm; } diff --git a/src/main/java/flipnote/group/domain/model/member/GroupRole.java b/src/main/java/flipnote/group/domain/model/member/GroupMemberRole.java similarity index 75% rename from src/main/java/flipnote/group/domain/model/member/GroupRole.java rename to src/main/java/flipnote/group/domain/model/member/GroupMemberRole.java index 9506fd7..4ca3784 100644 --- a/src/main/java/flipnote/group/domain/model/member/GroupRole.java +++ b/src/main/java/flipnote/group/domain/model/member/GroupMemberRole.java @@ -1,5 +1,5 @@ package flipnote.group.domain.model.member; -public enum GroupRole { +public enum GroupMemberRole { OWNER, HEAD_MANAGER, MANAGER, STAFF, MEMBER } diff --git a/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java new file mode 100644 index 0000000..4da80d7 --- /dev/null +++ b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupMemberRepository.java @@ -0,0 +1,8 @@ +package flipnote.group.infrastructure.persistence.jpa; + +import org.springframework.data.jpa.repository.JpaRepository; + +import flipnote.group.adapter.out.entity.GroupMemberEntity; + +public interface GroupMemberRepository extends JpaRepository { +} diff --git a/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java new file mode 100644 index 0000000..2452a5a --- /dev/null +++ b/src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java @@ -0,0 +1,8 @@ +package flipnote.group.infrastructure.persistence.jpa; + +import org.springframework.data.jpa.repository.JpaRepository; + +import flipnote.group.adapter.out.entity.GroupEntity; + +public interface GroupRepository extends JpaRepository { +} From c18522044278c6db5d5af00d6922f034c7100ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 4 Feb 2026 18:21:34 +0900 Subject: [PATCH 04/10] =?UTF-8?q?Feat:=20baseEntity=20=EB=82=B4=EC=9A=A9?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/infrastructure/config/AuditingConfig.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/flipnote/group/infrastructure/config/AuditingConfig.java diff --git a/src/main/java/flipnote/group/infrastructure/config/AuditingConfig.java b/src/main/java/flipnote/group/infrastructure/config/AuditingConfig.java new file mode 100644 index 0000000..57927c5 --- /dev/null +++ b/src/main/java/flipnote/group/infrastructure/config/AuditingConfig.java @@ -0,0 +1,13 @@ +package flipnote.group.infrastructure.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +/** + * + */ +@EnableJpaAuditing +@Configuration +public class AuditingConfig { + +} From 19fcda159a49dc3c51432fb9d25255dc295684f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Wed, 4 Feb 2026 22:24:41 +0900 Subject: [PATCH 05/10] =?UTF-8?q?Refactor:=20imageUrl=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20imageId=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flipnote/group/adapter/in/web/GroupController.java | 2 +- .../flipnote/group/adapter/out/entity/GroupEntity.java | 6 +++--- .../adapter/out/persistence/mapper/GroupMapper.java | 2 +- .../group/api/dto/request/CreateGroupRequestDto.java | 2 +- .../port/in/command/CreateGroupCommand.java | 2 +- .../java/flipnote/group/domain/model/group/Group.java | 10 +++++----- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/flipnote/group/adapter/in/web/GroupController.java b/src/main/java/flipnote/group/adapter/in/web/GroupController.java index f91c424..4972362 100644 --- a/src/main/java/flipnote/group/adapter/in/web/GroupController.java +++ b/src/main/java/flipnote/group/adapter/in/web/GroupController.java @@ -39,7 +39,7 @@ public ResponseEntity createGroup( req.joinPolicy(), req.visibility(), req.maxMember(), - req.imageUrl() + req.imageRefId() ); var result = createGroupUseCase.create(cmd, userId); diff --git a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java index 519a20c..c3e9968 100644 --- a/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java +++ b/src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java @@ -49,21 +49,21 @@ public class GroupEntity extends BaseEntity { @Column(nullable = false) private Integer maxMember; - private String imageUrl; + private Long imageRefId; @Column(nullable = false) private Integer memberCount; @Builder private GroupEntity(String name, Category category, String description, JoinPolicy joinPolicy, Visibility visibility, - Integer maxMember, String imageUrl, Integer memberCount) { + Integer maxMember, Long imageRefId, Integer memberCount) { this.name = name; this.category = category; this.description = description; this.joinPolicy = joinPolicy; this.visibility = visibility; this.maxMember = maxMember; - this.imageUrl = imageUrl; + this.imageRefId = imageRefId; this.memberCount = memberCount; } } diff --git a/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java b/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java index aa35f4f..c89f7f0 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/mapper/GroupMapper.java @@ -24,7 +24,7 @@ public static GroupEntity createNewEntity(Group domain) { .joinPolicy(domain.getJoinPolicy()) .visibility(domain.getVisibility()) .maxMember(domain.getMaxMember()) - .imageUrl(domain.getImageUrl()) + .imageRefId(domain.getImageRefId()) .memberCount(domain.getMemberCount()) .build(); } diff --git a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java index 4b8cf45..ecd7cfd 100644 --- a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java +++ b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java @@ -15,5 +15,5 @@ public record CreateGroupRequestDto( @NotNull JoinPolicy joinPolicy, @NotNull Visibility visibility, @NotNull @Min(1) @Max(100) Integer maxMember, - String imageUrl + Long imageRefId ) {} diff --git a/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java b/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java index 94fab57..40a2f58 100644 --- a/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java +++ b/src/main/java/flipnote/group/application/port/in/command/CreateGroupCommand.java @@ -11,6 +11,6 @@ public record CreateGroupCommand( JoinPolicy joinPolicy, Visibility visibility, int maxMember, - String imageUrl + Long imageRefId ) { } diff --git a/src/main/java/flipnote/group/domain/model/group/Group.java b/src/main/java/flipnote/group/domain/model/group/Group.java index 85843c6..eb44fc8 100644 --- a/src/main/java/flipnote/group/domain/model/group/Group.java +++ b/src/main/java/flipnote/group/domain/model/group/Group.java @@ -21,7 +21,7 @@ public class Group { private Visibility visibility; private int maxMember; - private String imageUrl; + private Long imageRefId; private int memberCount; /** @@ -39,7 +39,7 @@ public static Group create(CreateGroupCommand cmd) { group.joinPolicy = cmd.joinPolicy(); group.visibility = cmd.visibility(); group.maxMember = cmd.maxMember(); - group.imageUrl = cmd.imageUrl(); + group.imageRefId = cmd.imageRefId(); group.memberCount = 1; @@ -55,7 +55,7 @@ public static Group create(CreateGroupCommand cmd) { * @param joinPolicy * @param visibility * @param maxMember - * @param imageUrl + * @param imageRefId * @param memberCount * @return */ @@ -67,7 +67,7 @@ public static Group getGroup( JoinPolicy joinPolicy, Visibility visibility, int maxMember, - String imageUrl, + Long imageRefId, int memberCount ) { Group g = new Group(); @@ -78,7 +78,7 @@ public static Group getGroup( g.joinPolicy = joinPolicy; g.visibility = visibility; g.maxMember = maxMember; - g.imageUrl = imageUrl; + g.imageRefId = imageRefId; g.memberCount = memberCount; return g; } From 7312a74376887f8ea4ac2db03a37fd94dd1e6d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Tue, 10 Feb 2026 00:35:16 +0900 Subject: [PATCH 06/10] =?UTF-8?q?Fix:=20test=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 43ffcf3..d7345da 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ ext { group = 'flipnote' version = '0.0.1-SNAPSHOT' -description = 'Image' +description = 'Group' java { toolchain { @@ -55,6 +55,7 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' testImplementation 'org.springframework.boot:spring-boot-starter-data-redis-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'com.h2database:h2' From 887e71b962f5e2d802914921d34512835d4e8199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Tue, 10 Feb 2026 00:50:18 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Fix:=20grpc=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index d7345da..0797b51 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,6 @@ dependencies { implementation 'com.google.protobuf:protobuf-java' implementation 'io.grpc:grpc-stub' - implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation 'org.springframework.boot:spring-boot-starter-webmvc' compileOnly 'org.projectlombok:lombok' @@ -67,7 +66,7 @@ dependencies { def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile -tasks.withType(JavaCompile).configureEach { +tasks.named('compileJava', JavaCompile) { options.generatedSourceOutputDirectory.set(querydslDir) } @@ -78,11 +77,11 @@ tasks.withType(JavaCompile).configureEach { */ protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.3" + artifact = "com.google.protobuf:protoc:4.33.2" } plugins { grpc { - artifact = "io.grpc:protoc-gen-grpc-java:1.64.0" + artifact = "io.grpc:protoc-gen-grpc-java:1.77.1" } } generateProtoTasks { From 9144f3312b12c5faded3af3814b5cae86d6ab81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Tue, 10 Feb 2026 00:52:02 +0900 Subject: [PATCH 08/10] =?UTF-8?q?Chore:=20=EA=B7=B8=EB=A3=B9=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=ED=81=AC=EA=B8=B0=20=EC=A0=9C=ED=95=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flipnote/group/api/dto/request/CreateGroupRequestDto.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java index ecd7cfd..833fd01 100644 --- a/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java +++ b/src/main/java/flipnote/group/api/dto/request/CreateGroupRequestDto.java @@ -7,9 +7,10 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; public record CreateGroupRequestDto( - @NotBlank String name, + @NotBlank @Size(max = 50) String name, @NotNull Category category, @NotBlank String description, @NotNull JoinPolicy joinPolicy, From 717b49c5be04f2b58dc2e2186dc2790ecd1a0f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Tue, 10 Feb 2026 00:53:18 +0900 Subject: [PATCH 09/10] =?UTF-8?q?Chore:=20=EA=B7=B8=EB=A3=B9=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=ED=81=AC=EA=B8=B0=20=EC=A0=9C=ED=95=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/flipnote/group/domain/model/group/Group.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/flipnote/group/domain/model/group/Group.java b/src/main/java/flipnote/group/domain/model/group/Group.java index eb44fc8..e65508d 100644 --- a/src/main/java/flipnote/group/domain/model/group/Group.java +++ b/src/main/java/flipnote/group/domain/model/group/Group.java @@ -106,5 +106,8 @@ private static void validate(CreateGroupCommand cmd) { if (cmd.description() == null || cmd.description().isBlank()) { throw new IllegalArgumentException("description required"); } + if (cmd.name().length() > 50) { + throw new IllegalArgumentException("name too long"); + } } } From 21231614a41fd02a579015cb3bd7e7d2e2463fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9D=EB=B2=94?= Date: Tue, 10 Feb 2026 00:56:58 +0900 Subject: [PATCH 10/10] =?UTF-8?q?Fix:=20=ED=95=B5=EC=82=AC=EA=B3=A0?= =?UTF-8?q?=EB=82=A0=20=EC=9C=84=EB=B0=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/GroupRepositoryAdapter.java | 10 +++++----- .../application/port/out/GroupRepositoryPort.java | 5 ++--- .../group/application/service/CreateGroupService.java | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java b/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java index 875bc6e..63d3372 100644 --- a/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java +++ b/src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java @@ -3,7 +3,9 @@ import org.springframework.stereotype.Repository; import flipnote.group.adapter.out.entity.GroupEntity; +import flipnote.group.adapter.out.persistence.mapper.GroupMapper; import flipnote.group.application.port.out.GroupRepositoryPort; +import flipnote.group.domain.model.group.Group; import flipnote.group.infrastructure.persistence.jpa.GroupRepository; import lombok.RequiredArgsConstructor; @@ -14,10 +16,8 @@ public class GroupRepositoryAdapter implements GroupRepositoryPort { private final GroupRepository groupRepository; @Override - public Long saveNewGroup(GroupEntity groupEntity) { - - GroupEntity group = groupRepository.save(groupEntity); - - return group.getId(); + public Long saveNewGroup(Group group) { + GroupEntity entity = GroupMapper.createNewEntity(group); + return groupRepository.save(entity).getId(); } } diff --git a/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java b/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java index bc00f90..b1a2f7c 100644 --- a/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java +++ b/src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java @@ -1,8 +1,7 @@ package flipnote.group.application.port.out; -import flipnote.group.adapter.out.entity.GroupEntity; -import flipnote.group.application.port.in.command.CreateGroupCommand; +import flipnote.group.domain.model.group.Group; public interface GroupRepositoryPort { - Long saveNewGroup(GroupEntity groupEntity); + Long saveNewGroup(Group group); } diff --git a/src/main/java/flipnote/group/application/service/CreateGroupService.java b/src/main/java/flipnote/group/application/service/CreateGroupService.java index 83b66ea..5ad6e36 100644 --- a/src/main/java/flipnote/group/application/service/CreateGroupService.java +++ b/src/main/java/flipnote/group/application/service/CreateGroupService.java @@ -33,7 +33,7 @@ public CreateGroupResult create(CreateGroupCommand cmd, Long userId) { var domainGroup = Group.create(cmd); //그룹 도메인 -> 엔티티 변환 후 저장 - Long groupId = groupRepository.saveNewGroup(GroupMapper.createNewEntity(domainGroup)); + Long groupId = groupRepository.saveNewGroup(domainGroup); //그룹 멤버 저장 groupMemberRepository.saveOwner(groupId, userId);