Skip to content
Merged
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
70 changes: 69 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ 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'
Expand All @@ -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 '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.named('compileJava', JavaCompile) {
options.generatedSourceOutputDirectory.set(querydslDir)
}

/**
* proto 컴파일 설정
* - .proto 위치: src/main/proto
* - 생성 위치: build/generated/...
*/
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.33.2"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.77.1"
}
}
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') {
Expand Down
Empty file.
50 changes: 50 additions & 0 deletions src/main/java/flipnote/group/adapter/in/web/GroupController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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<CreateGroupResponseDto> createGroup(
@RequestHeader("X-USER-ID") Long userId,
@RequestBody @Valid CreateGroupRequestDto req) {

CreateGroupCommand cmd = new CreateGroupCommand(
req.name(),
req.category(),
req.description(),
req.joinPolicy(),
req.visibility(),
req.maxMember(),
req.imageRefId()
);

var result = createGroupUseCase.create(cmd, userId);
CreateGroupResponseDto res = CreateGroupResponseDto.from(result.groupId());
return ResponseEntity.ok(res);
}

}
Empty file.
69 changes: 69 additions & 0 deletions src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package flipnote.group.adapter.out.entity;

import flipnote.group.application.port.in.command.CreateGroupCommand;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용되지 않는 import: CreateGroupCommand

GroupEntity에서 CreateGroupCommand를 사용하지 않으므로 이 import를 제거해 주세요. 또한 persistence 계층 엔티티가 application port에 의존하는 것은 의존성 방향상 바람직하지 않습니다.

♻️ 제안
-import flipnote.group.application.port.in.command.CreateGroupCommand;
📝 Committable suggestion

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

Suggested change
import flipnote.group.application.port.in.command.CreateGroupCommand;
🤖 Prompt for AI Agents
In `@src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java` at line 3,
GroupEntity currently imports CreateGroupCommand which is unused and violates
dependency direction; remove the unused import statement for CreateGroupCommand
from GroupEntity and ensure the persistence entity does not reference any
application port types (replace any usage with entity/DTO classes or mapping
logic inside application/service layers if present); verify there are no
remaining references to CreateGroupCommand inside the GroupEntity class or its
methods and move any transformation logic that depends on application-layer
types into the appropriate application/service mapper.

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;
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 = "app_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;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private JoinPolicy joinPolicy;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Visibility visibility;

@Column(nullable = false)
private Integer maxMember;

private Long imageRefId;

@Column(nullable = false)
private Integer memberCount;

@Builder
private GroupEntity(String name, Category category, String description, JoinPolicy joinPolicy, Visibility visibility,
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.imageRefId = imageRefId;
this.memberCount = memberCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package flipnote.group.adapter.out.entity;

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;
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 extends BaseEntity {

@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 GroupMemberRole role;

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

/**
* 오너인 경우
* @param groupId
* @param userId
* @return
*/
public static GroupMemberEntity createOwner(Long groupId, Long userId) {
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)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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.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;

@Repository
@RequiredArgsConstructor
public class GroupRepositoryAdapter implements GroupRepositoryPort {

private final GroupRepository groupRepository;

@Override
public Long saveNewGroup(Group group) {
GroupEntity entity = GroupMapper.createNewEntity(group);
return groupRepository.save(entity).getId();
}
}
Original file line number Diff line number Diff line change
@@ -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())
.imageRefId(domain.getImageRefId())
.memberCount(domain.getMemberCount())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Empty file.
Loading