Repository navigation
[Feat] 약 상세 정보 조회 API + 메모장 자동 생성 기능 추가 #54
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
8bb7097
7a30246
614e2d8
2b618ec
be77db2
0de36dc
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,21 @@ | ||
| # Git 워크플로우 규칙 | ||
|
|
||
| ## 커밋 타이밍 | ||
| - 커밋/푸시는 사용자가 명시적으로 요청할 때만 수행한다 | ||
| - 파일 수정 후 임의로 커밋/푸시하지 않는다 | ||
|
|
||
| ## 커밋 분리 원칙 | ||
| 한 번에 전부 커밋하지 않는다. 아래 순서로 레이어별 분리: | ||
|
|
||
| 1. `entity` / `enums` 변경 시 | ||
| 2. `dto` (request / response) | ||
| 3. `converter` | ||
| 4. `repository` | ||
| 5. `service` (command / query 각각 별도) | ||
| 6. `controller` | ||
| 7. `config` (SecurityConfig 등) | ||
|
|
||
| ## 커밋 메시지 | ||
| - 본문은 한글로 작성한다 | ||
| - 기술 용어(DTO, OCR, ES, JWT 등)는 영어 그대로 쓴다 — 괄호로 번역 추가하지 않는다 | ||
| - 접두사: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.piuda.callcare.domain.medication.dto.response; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| @Schema(description = "약 상세 정보 응답 — drugName은 항상 반환, DrugInfo 미연결 시 의약품 정보 필드 null") | ||
| public record MedicationDetailResponse( | ||
|
|
||
| @Schema(description = "약품명") | ||
| String drugName, | ||
|
|
||
| @Schema(description = "효능·효과 (없으면 null)") | ||
| String efcyQesitm, | ||
|
|
||
| @Schema(description = "용법·용량 (없으면 null)") | ||
| String useMethodQesitm, | ||
|
|
||
| @Schema(description = "주의사항 (없으면 null)") | ||
| String atpnQesitm, | ||
|
|
||
| @Schema(description = "부작용 (없으면 null)") | ||
| String seQesitm | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.piuda.callcare.domain.medication.service.query; | ||
|
|
||
| import com.piuda.callcare.domain.medication.converter.MedicationConverter; | ||
| import com.piuda.callcare.domain.medication.dto.response.MedicationDetailResponse; | ||
| import com.piuda.callcare.domain.medication.entity.Medication; | ||
| import com.piuda.callcare.domain.medication.repository.MedicationRepository; | ||
| import com.piuda.callcare.global.exception.CallCareException; | ||
| import com.piuda.callcare.global.exception.ErrorCode; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @Transactional(readOnly = true) | ||
| @RequiredArgsConstructor | ||
| public class MedicationQueryService { | ||
|
|
||
| private final MedicationRepository medicationRepository; | ||
| private final MedicationConverter medicationConverter; | ||
|
|
||
| public MedicationDetailResponse getDetail(Long userId, Long medicationId) { | ||
| Medication medication = medicationRepository.findById(medicationId) | ||
| .orElseThrow(() -> new CallCareException(ErrorCode.MEDICATION_NOT_FOUND)); | ||
| if (userId != null && !medication.getSenior().getUser().getId().equals(userId)) { | ||
| throw new CallCareException(ErrorCode.FORBIDDEN); | ||
| } | ||
| return medicationConverter.toDetailResponse(medication); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,7 +43,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | |||||||
| .requestMatchers(HttpMethod.GET, "/api/conflicts/**").permitAll() | ||||||||
| .requestMatchers(HttpMethod.POST, "/api/conflicts/**").permitAll() | ||||||||
| .requestMatchers(HttpMethod.POST, "/api/ocr/**").permitAll() | ||||||||
| // TODO: 인증 필터 도입 후 제거하고, userId 기반 소유권 검증으로 전환 | ||||||||
| .requestMatchers(HttpMethod.POST, "/api/medications/batch").permitAll() | ||||||||
| .requestMatchers(HttpMethod.GET, "/api/medications/**").permitAll() | ||||||||
|
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. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🔒 권장 사항1. TODO 주석 추가 (즉시, 기존 패턴 일관성): + // TODO: 인증 필터 도입 시 제거하고, 로그인 사용자의 seniorId 소유권 검증으로 전환
.requestMatchers(HttpMethod.GET, "/api/medications/**").permitAll()2. 인증 도입 시 소유권 검증 추가 (MedicationQueryService + MedicationController): // MedicationController
public ResponseEntity<ApiResponse<MedicationDetailResponse>> getDetail(
+ `@AuthenticationPrincipal` Long userId,
`@PathVariable` Long medicationId
) {
- return ResponseUtils.ok(medicationQueryService.getDetail(medicationId));
+ return ResponseUtils.ok(medicationQueryService.getDetail(medicationId, userId));
} // MedicationQueryService
-public MedicationDetailResponse getDetail(Long medicationId) {
+public MedicationDetailResponse getDetail(Long medicationId, Long userId) {
Medication medication = medicationRepository.findById(medicationId)
.orElseThrow(() -> new CallCareException(ErrorCode.MEDICATION_NOT_FOUND));
+ if (!medication.getSenior().getUser().getId().equals(userId)) {
+ throw new CallCareException(ErrorCode.FORBIDDEN);
+ }
return medicationConverter.toDetailResponse(medication);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
| .anyRequest().authenticated() | ||||||||
| ) | ||||||||
| .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | ||||||||
|
|
||||||||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
PR 목표와 구현이 불일치합니다 — DrugInfo 미연결 시 응답 처리 방식 확인 필요
PR 목표는 "DrugInfo가 연결되지 않은 경우 전체 상세 응답을 null로 반환"이라고 명시하지만, 현재 구현은
drugName을 포함한 non-null 응답을 반환합니다. 또한MedicationDetailResponse의@Schema설명인 "DrugInfo 미연결 시 전 필드 null"과도 일치하지 않습니다 (drugName이 설정됨).두 가지 해석이 가능합니다:
drugInfo == null일 때null반환drugName은 유지하되 Schema/PR 목표 업데이트💡 옵션 1: PR 목표에 맞추는 경우
if (drugInfo == null) { - return new MedicationDetailResponse(medication.getDrugName(), null, null, null, null); + return null; }💡 옵션 2: 현재 구현을 유지하는 경우 — Schema 설명 수정
Based on learnings, if multiple interpretations of a task exist, present the alternatives rather than choosing silently.
🤖 Prompt for AI Agents
Source: Learnings