Skip to content

Commit 029684a

Browse files
whqtkerlsy1307Gyuhyeok99sukangpunchJAEHEE25
authored
[RELEASE] 260219 릴리즈 (#678)
* feat: 파견 대학 테이블명 변경 및 협정 대학 테이블 추가하는 DDL 작성 (#620) * feat: 파견 대학 테이블명 변경 및 협정 대학 테이블 추가하는 DDL 작성 * refactor: 테이블명 변경 및 추가에 따른 엔티티 생성 - 또한 목데이터 생성 로직 수정 * test: 테스트 코드에서 University -> HostUniversity로 변경 * chore: 중복 인덱스 생성 방지를 위해 인덱스 생성 제거 - FK 제약조건을 추가할 때 인덱스가 없다면 알아서 추가하기 때문 * chore: home_university 테이블에 created/updated_at 추가 * refactor: 잘못 설정되었던 테이블 간 연관 관계 재설정 (#622) * refactor: home_university와 university_info_for_apply가 FK 관계를 가지도록 * chore: FK 변경에 따른 목데이터 수정 * test: 테스트 픽스터 수정 * refactor: 대학 검색 응답 수정 (#624) * refactor: home_university와 university_info_for_apply가 FK 관계를 가지도록 * chore: FK 변경에 따른 목데이터 수정 * refactor: 필터 검색 엔드포인트 삭제 * refactor: 필터 검색 관련 서비스 로직 삭제 * refactor: 필터 검색 관련 레포지토리 메서드 삭제 * refactor: 필터 검색 관련 DTO 삭제 * test: 필터 검색 관련 테스트 코드 삭제 * refactor: 지원 대학 관련 응답에 협정 대학 이름 추가 * test: 지원 대학 응답 수정에 따른 테스트 수정 * refactor: 간접 참조 대신 연관관계 추가 - N+1 방지를 위해 fetch join도 추가 * test: 간접 참조 방식에서 연관 관계 설정으로 따른 테스트 코드 수정 * chore: 목데이터에서 지원 대학 테이블에 협정 대학 ID를 설정하도록 * test: home university fixture 추가 * refactor: home university에 대한 fetch join 추가 * refactor: s3 버전 업그레이드 및 로직 수정 (#608) * refactor: s3 sdk 버전 업그레이드 - 의존성 수정 - 버전 업그레이드에 따른 코드 수정 * refactor: 이미지 이외의 파일 관리를 위해 ImgType 의미 명확하도록 수정 - ImgType에서 UploadType으로 변경 - 해당되는 파일 모두 수정 * refactor: s3 테스트 코드 추가 * fix: s3 access-key, secret-key 최신화, 버킷 명칭 올바르게 수정 * fix: ChatService Test 변경점 반영, S3ServiceTest 단위 테스트로 변경 - images->files로 디렉토리 경로 수정 * fix: 이중 비동기 실행문제 해결 - @async에 전적으로 위임 * refactor: S3Service error 메시지 NPE 가능성 제거 * refactor: 수정사항 반영 - UploadType -> UploadPath로 명칭변경 - 컨벤션 수정(미사용 변수 삭제, 들여쓰기, 명칭변경) * fix: 테스트 코드 오류 수정 - 내부 로직에서 사용하는 fileUploadService 정의 * refactor: 수정사항 반영 - 파일 확장자 상수화 - 확장자 확인로직, 채팅이면 모든 파일 허용, 이미지 확인까지 모두 enum에서 관리 - MultipartFile이 비동기 과정에서 유실되지 않도록 byte로 변환해서 전달 - UrlPrefixResponse PascalCase로 변경 * refactor: 컨벤션 수정 - 사용하지 않는 import문 삭제 * refactor: 리프레시 토큰 만료시 쿠키 삭제 (#628) * refactor: 리프레시 토큰 만료시 쿠키 삭제 * refactor: 인증 전용 예외 생성 * refactor: 멘토링 조회 응답에 mentoringId 필드 추가 (#638) * feat: WebSocket 로깅 인터셉터 작성 (#635) * feat: WebSocket 로깅 인터셉터 작성 * refactor: Principal 명시적 형 변환 대신 null 체크하여 형 변환 * feat: 어드민에서 파견 대학을 관리하도록 (#633) * feat: 파견 대학 CRUD 관련 ErrorCode 추가 - HOST_UNIVERSITY_HAS_REFERENCES : 파견 대학 삭제 시 해당 대학을 참조하는 UnivApplyInfo가 존재하는 경우 * feat: 파견 대학 관련 정보를 업데이트하는 도메인 메서드 작성 * feat: 조회 관련 Repository 메서드 구현 * feat: 파견 대학 검색 관련 QueryDSL로 구현 * feat: 어드민 파견 대학 CRUD 관련 DTO 작성 * feat: country 조회 관련 로직 추가 및 ErrorCode 추가 * feat: 어드민 파견 대학 CRUD 관련 서비스 로직 작성 * feat: 어드민 파견 대학 관련 컨트롤러 작성 * test: 어드민 파견 대학 관리 관련 테스트 작성 * refactor: 엔드포인트의 path variable 이름 변경 - id -> host-university-id * refactor: PageResponse 응답 객체를 사용하도록 * test: 응답 변경에 따른 테스트 코드 수정 * fix: host_university 테이블의 korean_name 필드에 unique key 추가 (#645) * fix: host_university 테이블의 korean_name 필드에 unique key 쿠가 * test: test용 hostUniversityRepository 생성 * test: 고유한 korean_name을 가진 host university 객체를 사용하도록 * fix: 멘토 지원서 승인 시 유저 Role 을 Mentor로 승격 (#639) * fix: 멘토 지원서 승인 시 유저 Role 을 Mentor로 승격 * fix: 멘토 지원서 승인 시 멘토 생성 * fix: 멘토의 introduction, passTip null 허용하도록 수정 - not null 인 필드에 빈문자열로 값을 채우는 것 보다, null 허용이 더 의미 있다 판단하여 null 을 허용하도록 하였습니다. * fix: 사용하지 않는 멘토 생성 api 제거 - 멘토 생성의 주체가 어드민으로 변경되어 Mentor 도메인의 Mentor 생성 api 를 제거 * feat: 멘토 지원서 승인 예외처리 추가 - 중복 멘토 생성 예외 처리 및 테스트 추가 * refactor: Mentor 생성 시 null 전달 제거 * refactor: 멘토 지원서 승낙 시, 검증 후 승격 및 멘토 생성 * chore: 스크립트 버전 수정 (#651) * chore: 스크립트 버전 수정 * test: korean_name 컬럼 UK 관련 테스트 코드 수정 * feat: test skill 추가 (#647) * feat: serena MCP 추가 * feat: test skill 추가 * feat: hook 추가 - 응답 대기시 알람발송 - 컨벤션 어겼을 때 훅 작동 * feat: 안쓰는 파일 제거 * fix: 게시글 중복 생성 방지 (#649) * fix: 게시글 중복 생성 방지 - Redis 패키지 및 로직 정리 * fix: 게시글 중복 생성 방지 - 게시글 중복 요청 방지 Redis 로직 추가 * refactor: 게시글 중복 생성 방지 * chore: testcontainer 버전 업 (#659) * chore: windows에서도 hook이 동작하도록 (#655) * refactor: 오래된 이미지 삭제 후 이미지 pull하도록 변경 (#653) refactor: 오래된 이미지 삭제 후 이미지 pull하도록 변경 (#653) - 추가로 이미지는 5개 -> 2개 보관하도록 변경 * refactor: 멘토 도메인 응답의 사용자 id를 siteUserId로 통일 (#665) * refactor: 멘토 관련 id응답은 모두 site-user-id가 되도록 수정 * test: 멘토 관련 테스트 코드 수정 * refactor: 채팅 도메인 응답의 사용자 관련 id를 siteUserId로 통일 (#666) * refactor: 채팅 관련 응답에서 사용자 관련 Id를 siteUserId로 통일 * refactor: siteUserId를 포함하도록 서비스 코드 수정 * test: 사용자 id로 응답 통일 관련 테스트 수정 * feat: 전체 뉴스를 조회하는 API 구현 (#674) * feat: 전체 news 조회 API 구현 - 기존 API에 author-id를 선택적으로 받도록 * test: 전체 news 조회 관련 테스트 코드 작성 * refactor: 날짜 오름차순으로 news 조회하는 JPA 메서드 추가 * refactor: 뉴스 조회 API를 하나로 통합 - 서비스 계층에서 siteUserId == null을 기준으로 분기하도록 * refactor: 컨트롤러 계층에서 분기문 제거 - 분기를 서비스 계층에게 위임했음 * test: 뉴스 조회 관련 테스트 코드 수정 * chore: 누락된 제약 조건을 추가하는 스크립트 작성 (#676) --------- Co-authored-by: Yeon <84384499+lsy1307@users.noreply.github.com> Co-authored-by: 황규혁 <126947828+Gyuhyeok99@users.noreply.github.com> Co-authored-by: hyungjun <115551339+sukangpunch@users.noreply.github.com> Co-authored-by: 정재희 <y2hjjh@naver.com>
1 parent 56c07ab commit 029684a

File tree

124 files changed

+2658
-1134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+2658
-1134
lines changed

.claude/hooks/notify.ps1

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Add-Type -AssemblyName System.Windows.Forms
2+
$n = New-Object System.Windows.Forms.NotifyIcon
3+
$n.Icon = [System.Drawing.SystemIcons]::Information
4+
$n.Visible = $true
5+
$n.BalloonTipTitle = "Claude Code"
6+
$n.BalloonTipText = "Awaiting your input"
7+
$n.ShowBalloonTip(5000)
8+
Start-Sleep -Milliseconds 5100
9+
$n.Dispose()

.claude/hooks/notify.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import platform
4+
import subprocess
5+
6+
system = platform.system()
7+
script_dir = os.path.dirname(os.path.abspath(__file__))
8+
9+
if system == "Darwin":
10+
subprocess.run([
11+
"osascript", "-e",
12+
'display notification "Awaiting your input" with title "Claude Code"'
13+
])
14+
elif system == "Windows":
15+
ps1_path = os.path.join(script_dir, "notify.ps1")
16+
subprocess.run([
17+
"powershell", "-NoProfile", "-ExecutionPolicy", "Bypass",
18+
"-File", ps1_path
19+
])

.claude/hooks/post-edit-check.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python3
2+
import json
3+
import sys
4+
import re
5+
6+
data = json.load(sys.stdin)
7+
file_path = data.get("tool_input", {}).get("file_path", "")
8+
9+
if not file_path.endswith(".java") or not file_path:
10+
sys.exit(0)
11+
12+
try:
13+
with open(file_path) as f:
14+
content = f.read()
15+
lines = content.split("\n")
16+
except Exception:
17+
sys.exit(0)
18+
19+
warnings = []
20+
21+
# 1. 와일드카드 import 체크
22+
for i, line in enumerate(lines, 1):
23+
if re.match(r"\s*import\s+.*\.\*;", line):
24+
warnings.append(f"L{i}: 와일드카드 import 발견 -> 명시적 import 필요")
25+
26+
# 2. 파일 끝 줄바꿈 체크
27+
if content and not content.endswith("\n"):
28+
warnings.append("파일 끝 줄바꿈 누락")
29+
30+
# 3. Entity 클래스의 @Column 체크
31+
if "@Entity" in content:
32+
field_pattern = re.compile(r"^\s+private\s+\w+(?:<[^>]+>)?\s+\w+;")
33+
relation_annotations = {
34+
"@Column", "@Id", "@ManyToOne", "@OneToMany",
35+
"@JoinColumn", "@OneToOne", "@ManyToMany",
36+
"@Transient", "@Version", "@Embedded", "@EmbeddedId",
37+
}
38+
for i, line in enumerate(lines):
39+
if field_pattern.match(line):
40+
preceding = "\n".join(lines[max(0, i - 5):i])
41+
has_annotation = any(ann in preceding for ann in relation_annotations)
42+
if not has_annotation:
43+
warnings.append(f"L{i + 1}: Entity 필드에 @Column 누락 가능성: {line.strip()}")
44+
45+
if warnings:
46+
print(f"[컨벤션 체크 - {file_path.split('/')[-1]}]")
47+
for w in warnings:
48+
print(f" - {w}")

.claude/settings.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"env": {
3+
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
4+
},
5+
"hooks": {
6+
"Notification": [
7+
{
8+
"matcher": "",
9+
"hooks": [
10+
{
11+
"type": "command",
12+
"command": "python3 .claude/hooks/notify.py 2>/dev/null || python .claude/hooks/notify.py"
13+
}
14+
]
15+
}
16+
],
17+
"PostToolUse": [
18+
{
19+
"matcher": "Edit|Write",
20+
"hooks": [
21+
{
22+
"type": "command",
23+
"command": "python3 .claude/hooks/post-edit-check.py 2>/dev/null || python .claude/hooks/post-edit-check.py"
24+
}
25+
]
26+
}
27+
]
28+
}
29+
}

.claude/skills/test/SKILL.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
name: test
3+
description: 테스트 코드를 작성하거나 수정할 때 이 프로젝트의 테스트 컨벤션과 패턴을 참고합니다
4+
---
5+
6+
# 테스트 코드 작성 가이드
7+
8+
## 테스트 기본 설정
9+
10+
모든 통합 테스트는 `@TestContainerSpringBootTest` 어노테이션을 사용합니다.
11+
12+
```java
13+
@TestContainerSpringBootTest
14+
@DisplayName("채팅 서비스 테스트")
15+
class ChatServiceTest {
16+
// 테스트 코드
17+
}
18+
```
19+
20+
**제공 기능:**
21+
- MySQL, Redis 자동 실행
22+
- Spring Boot 컨텍스트 로드
23+
- 테스트 후 자동 DB 초기화
24+
- JUnit 5 기반
25+
26+
## Fixture 패턴
27+
28+
테스트 데이터는 Fixture로 생성합니다 (FixtureBuilder + Fixture 패턴).
29+
30+
**위치:** `src/test/java/com/example/solidconnection/[domain]/fixture/`
31+
32+
```
33+
fixture/
34+
├── [Entity]FixtureBuilder.java # Builder 패턴 구현
35+
└── [Entity]Fixture.java # 편의 메서드 제공
36+
```
37+
38+
### 예제: ChatRoomFixtureBuilder
39+
40+
```java
41+
@TestComponent
42+
@RequiredArgsConstructor
43+
public class ChatRoomFixtureBuilder {
44+
45+
private final ChatRoomRepository chatRoomRepository;
46+
47+
private boolean isGroup;
48+
private Long mentoringId;
49+
50+
public ChatRoomFixtureBuilder chatRoom() {
51+
return new ChatRoomFixtureBuilder(chatRoomRepository);
52+
}
53+
54+
public ChatRoomFixtureBuilder isGroup(boolean isGroup) {
55+
this.isGroup = isGroup;
56+
return this;
57+
}
58+
59+
public ChatRoomFixtureBuilder mentoringId(long mentoringId) {
60+
this.mentoringId = mentoringId;
61+
return this;
62+
}
63+
64+
public ChatRoom create() {
65+
ChatRoom chatRoom = new ChatRoom(mentoringId, isGroup);
66+
return chatRoomRepository.save(chatRoom); // DB 저장
67+
}
68+
}
69+
```
70+
71+
### 예제: ChatRoomFixture
72+
73+
```java
74+
@TestComponent
75+
@RequiredArgsConstructor
76+
public class ChatRoomFixture {
77+
78+
private final ChatRoomFixtureBuilder chatRoomFixtureBuilder;
79+
80+
// 편의 메서드: 기본값으로 생성
81+
public ChatRoom 채팅방(boolean isGroup) {
82+
return chatRoomFixtureBuilder.chatRoom()
83+
.isGroup(isGroup)
84+
.create();
85+
}
86+
87+
public ChatRoom 멘토링_채팅방(long mentoringId) {
88+
return chatRoomFixtureBuilder.chatRoom()
89+
.mentoringId(mentoringId)
90+
.isGroup(false)
91+
.create();
92+
}
93+
}
94+
```
95+
96+
**편의 메서드 작성 팁:**
97+
98+
- 한국어 메서드명 사용 (가독성)
99+
- 자주 사용되는 기본값 조합만 제공
100+
- Builder를 조합하여 필요한 데이터 설정
101+
102+
### 테스트에서 사용
103+
104+
```java
105+
@TestContainerSpringBootTest
106+
class ChatServiceTest {
107+
108+
@Autowired
109+
private ChatRoomFixture chatRoomFixture;
110+
111+
@Test
112+
void 채팅방을_생성할_수_있다() {
113+
// 편의 메서드 사용
114+
ChatRoom room = chatRoomFixture.채팅방(false);
115+
116+
// Builder 직접 사용
117+
ChatRoom customRoom = chatRoomFixture.chatRoomFixtureBuilder.chatRoom()
118+
.isGroup(true)
119+
.mentoringId(100L)
120+
.create();
121+
}
122+
}
123+
```
124+
125+
## 테스트 네이밍 컨벤션
126+
127+
### 테스트 메서드 네이밍 규칙
128+
129+
테스트 메서드명은 **한국어로 명확하게** 작성하며, 다음 패턴을 따릅니다:
130+
131+
#### 1. 정상 동작 테스트
132+
133+
```java
134+
// 패턴: 어떤_것을_하면_어떤_결과가_나온다
135+
@Test
136+
void 채팅방이_없으면_빈_목록을_반환한다() { ... }
137+
138+
@Test
139+
void 최신_메시지_순으로_정렬되어_조회한다() { ... }
140+
141+
@Test
142+
void 참여자는_메시지를_전송할_수_있다() { ... }
143+
144+
@Test
145+
void 페이징이_정상_작동한다() { ... }
146+
```
147+
148+
#### 2. 예외 테스트
149+
150+
```java
151+
// 패턴: 어떤_것을_하면_예외_응답을_반환한다
152+
@Test
153+
void 참여하지_않은_채팅방에_접근하면_예외_응답을_반환한다() { ... }
154+
155+
@Test
156+
void 존재하지_않는_사용자로_메시지를_전송하면_예외_응답을_반환한다() { ... }
157+
158+
@Test
159+
void 권한이_없으면_예외_응답을_반환한다() { ... }
160+
161+
@Test
162+
void 필수_파라미터가_없으면_예외_응답을_반환한다() { ... }
163+
```
164+
165+
## BDD 테스트 작성
166+
167+
테스트는 Given-When-Then 구조로 작성합니다.
168+
169+
```java
170+
@Test
171+
@DisplayName("최신 메시지순으로 채팅방 목록을 조회한다")
172+
void 최신_메시지_순으로_조회한다() {
173+
// Given: 테스트 사전 조건
174+
SiteUser user = siteUserFixture.사용자();
175+
ChatRoom room1 = chatRoomFixture.채팅방(false);
176+
ChatRoom room2 = chatRoomFixture.채팅방(false);
177+
chatMessageFixture.메시지("오래된 메시지", user.getId(), room1);
178+
chatMessageFixture.메시지("최신 메시지", user.getId(), room2);
179+
180+
// When: 실제 동작
181+
ChatRoomListResponse response = chatService.getChatRooms(user.getId());
182+
183+
// Then: 결과 검증
184+
assertAll(
185+
() -> assertThat(response.chatRooms()).hasSize(2),
186+
() -> assertThat(response.chatRooms().get(0).id()).isEqualTo(room2.getId())
187+
);
188+
}
189+
```
190+
191+
## 테스트 그룹화 (@Nested)
192+
193+
기능별로 테스트를 그룹화합니다.
194+
195+
```java
196+
@TestContainerSpringBootTest
197+
class ChatServiceTest {
198+
199+
@Nested
200+
@DisplayName("채팅방 목록 조회")
201+
class 채팅방_목록을_조회한다 {
202+
203+
@Test
204+
void 빈_목록을_반환한다() { ... }
205+
206+
@Test
207+
void 최신_메시지_순으로_조회한다() { ... }
208+
}
209+
210+
@Nested
211+
@DisplayName("채팅 메시지 전송")
212+
class 채팅_메시지를_전송한다 {
213+
214+
@BeforeEach
215+
void setUp() {
216+
// 이 그룹에만 적용되는 초기 설정
217+
}
218+
219+
@Test
220+
void 참여자는_메시지를_전송할_수_있다() { ... }
221+
}
222+
}
223+
```
224+
225+
## 자주 사용하는 Assertion
226+
227+
```java
228+
// 기본 검증
229+
assertThat(value).isEqualTo(expected);
230+
assertThat(value).isNotNull();
231+
232+
// 컬렉션
233+
assertThat(list).hasSize(3);
234+
assertThat(list).isEmpty();
235+
assertThat(list).contains(item);
236+
237+
// 예외 검증
238+
assertThatCode(() -> method())
239+
.isInstanceOf(CustomException.class)
240+
.hasMessage("error message");
241+
242+
// 복수 검증
243+
assertAll(
244+
() -> assertThat(a).isEqualTo(1),
245+
() -> assertThat(b).isEqualTo(2)
246+
);
247+
```

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ out/
3737
### VS Code ###
3838
.vscode/
3939

40+
### Claude Code ###
41+
.claude/settings.local.json
42+
4043
### YML ###
4144
application-secret.yml
4245
application-prod.yml

.serena/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/cache

0 commit comments

Comments
 (0)