Skip to content

Commit 0dd69fa

Browse files
authored
Feature/144 봉사자 to 기관 쪽지 송신 기능 개발 (#154)
* feat: 봉사자 to 기관 쪽지 송신 기능 레포지토리 구현 - 레포지토리 인터페이스 생성 - Jpa 레포지토리 생성 - 레포지토리 구현체 생성 * feat: 봉사자 to 기관 쪽지 송신 기능 서비스레이어 구현 - 유스케이스 생성 - 송신 클래스및 메서드 생성 - 요청 Dto 구현및 toEntity 변환 메서드 구 * feat: 봉사자 to 기관 쪽지 송신 기능 엔드포인트 구현 - 컨트롤러 구현 - 테스트 코드 작성및 검증 완료 * style(SendNoteToCenterTest): 잠재적 에러 예방을 위한 개행 추가 - 마지막 라인에 개행 추가 * chore: 코드 리뷰 사항 반영 - 엔티티에 존재하던 불필요한 백틱 제거 - 컨트롤러에 존재하던 불필요한 value 속성 지정 제거 - 불필요한 개행 제거
1 parent a33a5f7 commit 0dd69fa

File tree

11 files changed

+361
-34
lines changed

11 files changed

+361
-34
lines changed

src/main/java/com/somemore/domains/Note.java

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.somemore.note.controller;
2+
3+
import com.somemore.auth.annotation.CurrentUser;
4+
import com.somemore.global.common.response.ApiResponse;
5+
import com.somemore.note.dto.SendNoteToCenterRequestDto;
6+
import com.somemore.note.usecase.SendNoteToCenterUseCase;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import jakarta.validation.Valid;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.security.access.annotation.Secured;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestBody;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
import java.util.UUID;
18+
19+
@Tag(name = "Note Command API", description = "쪽지 송신 삭제 API")
20+
@RequiredArgsConstructor
21+
@RequestMapping("/api/note")
22+
@RestController
23+
public class NoteCommandApiController {
24+
25+
private final SendNoteToCenterUseCase sendNoteToCenterUseCase;
26+
27+
@Secured("ROLE_VOLUNTEER")
28+
@Operation(summary = "봉사자 to 기관 쪽지 송신")
29+
@PostMapping("/volunteer-to-center")
30+
public ApiResponse<Long> sendNoteToCenter(@CurrentUser UUID userId, @Valid @RequestBody SendNoteToCenterRequestDto requestDto) {
31+
32+
Long noteId = sendNoteToCenterUseCase.sendNoteToCenter(userId, requestDto);
33+
34+
return ApiResponse.ok(201, noteId, "쪽지 송신 성공");
35+
}
36+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.somemore.note.domain;
2+
3+
import com.somemore.global.common.BaseEntity;
4+
import com.somemore.interestcenter.domain.InterestCenter;
5+
import jakarta.persistence.*;
6+
import lombok.AccessLevel;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
import java.util.UUID;
12+
13+
import static jakarta.persistence.GenerationType.IDENTITY;
14+
15+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
16+
@Getter
17+
@Entity
18+
@Table(name = "note")
19+
public class Note extends BaseEntity {
20+
@Id
21+
@GeneratedValue(strategy = IDENTITY)
22+
private Long id;
23+
24+
@Column(name = "sender_id", nullable = false, length = 16)
25+
private UUID senderId;
26+
27+
@Column(name = "receiver_id", nullable = false, length = 16)
28+
private UUID receiverId;
29+
30+
@Column(name = "title", nullable = false)
31+
private String title;
32+
33+
@Lob
34+
@Column(name = "content", nullable = false)
35+
private String content;
36+
37+
@Column(name = "is_read", nullable = false)
38+
private Boolean isRead = false;
39+
40+
@Builder
41+
private Note(UUID senderId, UUID receiverId, String title, String content) {
42+
this.senderId = senderId;
43+
this.receiverId = receiverId;
44+
this.title = title;
45+
this.content = content;
46+
}
47+
48+
public static Note create(UUID senderId, UUID receiverId, String title, String content) {
49+
return Note.builder()
50+
.senderId(senderId)
51+
.receiverId(receiverId)
52+
.title(title)
53+
.content(content)
54+
.build();
55+
}
56+
57+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.somemore.note.dto;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
import com.somemore.note.domain.Note;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import jakarta.validation.constraints.NotNull;
8+
9+
import java.util.UUID;
10+
11+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
12+
public record SendNoteToCenterRequestDto(
13+
14+
@Schema(description = "쪽지 수신 기관 아이디", example = "123e4567-e89b-12d3-a456-426614174000")
15+
@NotNull(message = "수신 기관 아이디는 필수 값입니다.")
16+
UUID receiverId,
17+
18+
@Schema(description = "쪽지 제목", example = "이번주 참여할 봉사자인데 문의 드릴게 있습니다.")
19+
@NotNull(message = "쪽지 제목은 필수 값입니다.")
20+
String title,
21+
22+
@Schema(description = "쪽지 내용", example = "정확히 어떤 일을 도와드리는건가요?")
23+
@NotNull(message = "쪽지 내용은 필수 값입니다.")
24+
String content
25+
) {
26+
public Note toEntity(UUID senderId){
27+
return Note.create(senderId, receiverId, title, content);
28+
}
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.somemore.note.repository;
2+
3+
import com.somemore.note.domain.Note;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface NoteJpaRepository extends JpaRepository<Note, Long> {
9+
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.somemore.note.repository;
2+
3+
import com.somemore.note.domain.Note;
4+
5+
public interface NoteRepository {
6+
7+
Note save(Note note);
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.somemore.note.repository;
2+
3+
import com.somemore.note.domain.Note;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.stereotype.Repository;
6+
7+
@RequiredArgsConstructor
8+
@Repository
9+
public class NoteRepositoryImpl implements NoteRepository {
10+
11+
private final NoteJpaRepository noteJpaRepository;
12+
13+
@Override
14+
public Note save(Note note) {
15+
return noteJpaRepository.save(note);
16+
}
17+
18+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.somemore.note.service;
2+
3+
import com.somemore.center.usecase.query.CenterQueryUseCase;
4+
import com.somemore.note.domain.Note;
5+
import com.somemore.note.dto.SendNoteToCenterRequestDto;
6+
import com.somemore.note.repository.NoteRepository;
7+
import com.somemore.note.usecase.SendNoteToCenterUseCase;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
11+
12+
import java.util.UUID;
13+
14+
@RequiredArgsConstructor
15+
@Service
16+
@Transactional
17+
public class SendNoteToCenterService implements SendNoteToCenterUseCase {
18+
19+
private final NoteRepository noteRepository;
20+
private final CenterQueryUseCase centerQueryUseCase;
21+
22+
@Override
23+
public Long sendNoteToCenter(UUID senderId, SendNoteToCenterRequestDto requestDto) {
24+
25+
centerQueryUseCase.validateCenterExists(requestDto.receiverId());
26+
27+
Note note = requestDto.toEntity(senderId);
28+
noteRepository.save(note);
29+
return note.getId();
30+
}
31+
32+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.somemore.note.usecase;
2+
3+
import com.somemore.note.dto.SendNoteToCenterRequestDto;
4+
5+
import java.util.UUID;
6+
7+
public interface SendNoteToCenterUseCase {
8+
Long sendNoteToCenter(UUID senderId, SendNoteToCenterRequestDto requestDto);
9+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.somemore.note.controller;
2+
3+
import com.somemore.ControllerTestSupport;
4+
import com.somemore.WithMockCustomUser;
5+
import com.somemore.note.dto.SendNoteToCenterRequestDto;
6+
import com.somemore.note.usecase.SendNoteToCenterUseCase;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.boot.test.mock.mockito.MockBean;
10+
import org.springframework.http.MediaType;
11+
12+
import java.util.UUID;
13+
14+
import static org.mockito.ArgumentMatchers.any;
15+
import static org.mockito.Mockito.when;
16+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
17+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
18+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
19+
20+
class NoteCommandApiControllerTest extends ControllerTestSupport{
21+
22+
@MockBean
23+
private SendNoteToCenterUseCase sendNoteToCenterUseCase;
24+
25+
@DisplayName("봉사자는 기관에게 쪽지를 보낼수 있다 (controller)")
26+
@Test
27+
@WithMockCustomUser
28+
void sendNoteToCenter_Success() throws Exception {
29+
// Given
30+
UUID receiverId = UUID.randomUUID();
31+
SendNoteToCenterRequestDto requestDto = new SendNoteToCenterRequestDto(
32+
receiverId,
33+
"쪽지 제목 문의 드릴게 있습니다.",
34+
"쪽지 내용"
35+
);
36+
37+
when(sendNoteToCenterUseCase.sendNoteToCenter(any(UUID.class), any(SendNoteToCenterRequestDto.class)))
38+
.thenReturn(1L);
39+
40+
// When & Then
41+
mockMvc.perform(post("/api/note/volunteer-to-center")
42+
.contentType(MediaType.APPLICATION_JSON)
43+
.content(objectMapper.writeValueAsString(requestDto)))
44+
.andExpect(status().isOk())
45+
.andExpect(jsonPath("$.code").value(201))
46+
.andExpect(jsonPath("$.data").value(1L))
47+
.andExpect(jsonPath("$.message").value("쪽지 송신 성공"));
48+
}
49+
50+
@DisplayName("쪽지 송신 시 누락된 정보가 있다면 송신을 할 수 없다")
51+
@Test
52+
void sendNoteToCenter_ValidationFail() throws Exception {
53+
// Given
54+
UUID userId = UUID.randomUUID();
55+
SendNoteToCenterRequestDto invalidRequestDto = new SendNoteToCenterRequestDto(
56+
null,
57+
null,
58+
null
59+
);
60+
61+
// When & Then
62+
mockMvc.perform(post("/api/note/volunteer-to-center")
63+
.contentType(MediaType.APPLICATION_JSON)
64+
.content(objectMapper.writeValueAsString(invalidRequestDto))
65+
.requestAttr("userId", userId))
66+
.andExpect(status().isBadRequest());
67+
}
68+
69+
}

0 commit comments

Comments
 (0)