-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: [FN-96][FN-97][FN-99][FN-110] 그룹 초대 기능 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
106446a
1db794d
b26f650
e601880
f2edbce
585395e
da44fc0
f9927d7
2f8958e
1f60691
393624f
bd3b8f8
2dfac03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package project.flipnote.common.config; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Arrays; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.redisson.Redisson; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.redisson.api.RedissonClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.redisson.config.Config; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Bean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.context.annotation.Configuration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.util.StringUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class RedissonConfig { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${spring.data.redis.host}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String host; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${spring.data.redis.port}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private int port; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${spring.data.redis.password:}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String password; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${spring.data.redis.cluster.nodes:}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String clusterNodes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion spring.redis.와 spring.data.redis. 동시 지원 + SSL 플래그 추가 제안 현재 모든 프로퍼티가 spring.data.redis.* 프리픽스를 사용합니다. Spring Boot와 서드파티 오토컨피그(예: Spring Data Redis, ShedLock의 RedisLockProvider)는 일반적으로 spring.redis.*를 기본으로 사용합니다. 양쪽 프리픽스를 모두 지원하면 설정 실수를 줄이고 호환성을 높일 수 있습니다. 또한 TLS 환경을 고려해 ssl 플래그도 주입받을 것을 권장합니다. 아래처럼 키 폴백과 ssl 플래그를 추가해 주세요. - @Value("${spring.data.redis.host}")
+ @Value("${spring.redis.host:${spring.data.redis.host}}")
private String host;
- @Value("${spring.data.redis.port}")
+ @Value("${spring.redis.port:${spring.data.redis.port}}")
private int port;
- @Value("${spring.data.redis.password:}")
+ @Value("${spring.redis.password:${spring.data.redis.password:}}")
private String password;
- @Value("${spring.data.redis.cluster.nodes:}")
+ @Value("${spring.redis.cluster.nodes:${spring.data.redis.cluster.nodes:}}")
private String clusterNodes;
+
+ @Value("${spring.redis.ssl:${spring.data.redis.ssl:false}}")
+ private boolean ssl;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Bean(destroyMethod = "shutdown") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public RedissonClient redissonClient() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Config config = new Config(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!clusterNodes.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.useClusterServers() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .addNodeAddress( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Arrays.stream(clusterNodes.split(",")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(String::trim) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter(s -> !s.isEmpty()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(addr -> addr.startsWith("redis://") || addr.startsWith("rediss://") ? addr : | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "redis://" + addr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toArray(String[]::new) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setPassword(StringUtils.hasText(password) ? password : null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| config.useSingleServer() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setAddress("redis://" + host + ":" + port) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .setPassword(StringUtils.hasText(password) ? password : null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion TLS(rediss://) 및 주소 스킴 일관성 처리 현재 클러스터/싱글 모두 스킴이 하드코딩되거나(싱글: redis://), 클러스터는 기존 스킴만 보존하고 기본값은 redis://로 붙입니다. ssl 설정에 따라 rediss:// 스킴을 일관되게 적용할 수 있도록 스킴 변수를 도입하는 것을 권장합니다. public RedissonClient redissonClient() {
Config config = new Config();
- if (!clusterNodes.isBlank()) {
+ final String scheme = ssl ? "rediss" : "redis";
+
+ if (!clusterNodes.isBlank()) {
config.useClusterServers()
.addNodeAddress(
Arrays.stream(clusterNodes.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
- .map(addr -> addr.startsWith("redis://") || addr.startsWith("rediss://") ? addr :
- "redis://" + addr)
+ .map(addr ->
+ (addr.startsWith("redis://") || addr.startsWith("rediss://"))
+ ? addr
+ : (scheme + "://" + addr)
+ )
.toArray(String[]::new)
)
.setPassword(StringUtils.hasText(password) ? password : null);
} else {
config.useSingleServer()
- .setAddress("redis://" + host + ":" + port)
+ .setAddress(scheme + "://" + host + ":" + port)
.setPassword(StringUtils.hasText(password) ? password : null);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Redisson.create(config); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package project.flipnote.common.config; | ||
|
|
||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.scheduling.annotation.EnableScheduling; | ||
|
|
||
| @EnableScheduling | ||
| @Configuration | ||
| public class SchedulerConfig { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package project.flipnote.common.config; | ||
|
|
||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
|
|
||
| import net.javacrumbs.shedlock.core.LockProvider; | ||
| import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider; | ||
| import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; | ||
|
|
||
| @EnableSchedulerLock(defaultLockAtMostFor = "PT30S") | ||
| @Configuration | ||
| public class ShedLockConfig { | ||
|
|
||
| @Bean | ||
| public LockProvider lockProvider(RedisConnectionFactory connectionFactory) { | ||
| return new RedisLockProvider(connectionFactory); | ||
| } | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| package project.flipnote.common.event; | ||
|
|
||
| public record UserRegisteredEvent( | ||
| Long userId, | ||
| String email | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| package project.flipnote.group.entity; | ||
|
|
||
| public enum GroupInvitationStatus { | ||
| PENDING, ACCEPTED, REJECTED | ||
| PENDING, ACCEPTED, REJECTED, EXPIRED; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,5 @@ | ||||||||||||||||||||||||||
| package project.flipnote.group.exception; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||
|
|
@@ -13,8 +12,7 @@ public enum GroupInvitationErrorCode implements ErrorCode { | |||||||||||||||||||||||||
| ALREADY_INVITED(HttpStatus.CONFLICT, "GROUP_INVITATION_001", "이미 초대된 사용자입니다."), | ||||||||||||||||||||||||||
| NO_INVITATION_PERMISSION(HttpStatus.FORBIDDEN, "GROUP_INVITATION_002", "해당 그룹에 초대할 권한이 없습니다."), | ||||||||||||||||||||||||||
| INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_INVITATION_003", "유효하지 않은 초대입니다."), | ||||||||||||||||||||||||||
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."), | ||||||||||||||||||||||||||
| ALREADY_GROUP_MEMBER(HttpStatus.CONFLICT, "GROUP_INVITATION_005", "이미 그룹 회원입니다."); | ||||||||||||||||||||||||||
| CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다."); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
Comment on lines
12
to
16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 만료된 초대(Expired) 시나리오용 에러 코드를 추가하는 것이 명확합니다 초대 만료 로직이 도입되었다면, 만료 케이스를 NOT_FOUND로 뭉뚱그리기보다 전용 에러코드로 구분하는 편이 API 소비자 관점에서 명확합니다. 아래와 같이 초대 만료 전용 코드를 추가하는 것을 제안합니다. public enum GroupInvitationErrorCode implements ErrorCode {
ALREADY_INVITED(HttpStatus.CONFLICT, "GROUP_INVITATION_001", "이미 초대된 사용자입니다."),
NO_INVITATION_PERMISSION(HttpStatus.FORBIDDEN, "GROUP_INVITATION_002", "해당 그룹에 초대할 권한이 없습니다."),
INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "GROUP_INVITATION_003", "유효하지 않은 초대입니다."),
+ INVITATION_EXPIRED(HttpStatus.GONE, "GROUP_INVITATION_005", "만료된 초대입니다."),
CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "GROUP_INVITATION_004", "본인을 초대할 수 없습니다.");
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| private final HttpStatus httpStatus; | ||||||||||||||||||||||||||
| private final String code; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
테스트 프로파일의 Redis 설정과 CI의 Redis 서비스가 일치하는지 확인 필요
CI에서는 비밀번호 없이 localhost:6379로 접근 가능한 Redis를 띄웁니다. 애플리케이션의 테스트 프로파일(application-test.yml 등)이 동일한 호스트/포트/비번 없이 접속하도록 설정돼 있는지 확인하세요. 또한 일부 라이브러리는 spring.redis.* 프리픽스를, 현재 RedissonConfig는 spring.data.redis.* 프리픽스를 사용합니다. 설정 키 불일치 시 접속 실패가 발생할 수 있습니다.
다음 스크립트로 리소스 내 Redis 설정 키를 점검해 주세요.
🏁 Script executed:
Length of output: 1250
테스트 프로파일에 Redis 설정 누락됨 – CI 서비스와 동기화 필요
현재 CI는 비밀번호 없이 localhost:6379로 Redis를 실행하지만, 리포지토리의 설정 파일에는 spring.data.redis.* 또는 spring.redis.* 관련 키가 전혀 없습니다. 또한 RedissonConfig는 spring.data.redis.* 프리픽스를 사용 중이므로, 테스트 프로파일에 명시적인 설정이 반드시 필요합니다.
조치 사항:
파일 위치:
🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 37-37: wrong indentation: expected 8 but found 10
(indentation)
🤖 Prompt for AI Agents
Redis 서비스 추가는 적절합니다만, YAML 들여쓰기 경고와 포트 매핑 들여쓰기 수정이 필요합니다
아래처럼 ports의 리스트 들여쓰기를 2칸 줄여주세요.
📝 Committable suggestion
🧰 Tools
🪛 YAMLlint (1.37.1)
[warning] 37-37: wrong indentation: expected 8 but found 10
(indentation)
🤖 Prompt for AI Agents