-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 캡슐 CRUD / IPFS 연동 #25
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
base: main
Are you sure you want to change the base?
Changes from all commits
c37ba30
3a1c26c
7034f14
8f79547
387948b
675afe7
11450e8
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,31 @@ | ||
| package com.dasom.MemoReal.domain.capsule.config; | ||
|
|
||
| import io.swagger.v3.oas.models.Components; | ||
| import io.swagger.v3.oas.models.OpenAPI; | ||
| import io.swagger.v3.oas.models.info.Info; | ||
| import org.springdoc.core.models.GroupedOpenApi; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| public class SwaggerConfig { | ||
|
|
||
| @Bean | ||
| public OpenAPI openAPI() { | ||
| return new OpenAPI() | ||
| .components(new Components()) | ||
| .info(new Info() | ||
| .title("API 문서") | ||
| .description("Swagger를 활용한 API 문서입니다.") | ||
| .version("1.0.0") | ||
| ); | ||
| } | ||
|
|
||
| @Bean | ||
| public GroupedOpenApi publicApi() { | ||
| return GroupedOpenApi.builder() | ||
| .group("v1-definition") | ||
| .pathsToMatch("/**") | ||
| .build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.dasom.MemoReal.domain.capsule.controller; | ||
|
|
||
| import com.dasom.MemoReal.domain.capsule.dto.CapsuleCreateRequestDto; | ||
| import com.dasom.MemoReal.domain.capsule.dto.CapsuleUpdateRequestDto; | ||
| import com.dasom.MemoReal.domain.capsule.entity.Capsule; | ||
| import com.dasom.MemoReal.domain.capsule.service.CapsuleService; | ||
| import com.dasom.MemoReal.domain.ipfs.service.IpfsService; | ||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/capsules") | ||
| @RequiredArgsConstructor | ||
| public class CapsuleController { | ||
|
|
||
| private final CapsuleService capsuleService; | ||
| private final IpfsService ipfsService; | ||
| private final ObjectMapper objectMapper; | ||
|
|
||
| @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | ||
| public ResponseEntity<Capsule> createCapsule( | ||
| @RequestPart("data") String data, | ||
| // 수동 데이터 입력 | ||
| // {"capsuleType":"일반","title":"제목","content":"내용","openTime":"2025-07-20T14:03:42.989Z"} | ||
| @RequestPart(value = "media", required = false) MultipartFile mediaFile | ||
| ) throws JsonProcessingException { | ||
| CapsuleCreateRequestDto dto = objectMapper.readValue(data, CapsuleCreateRequestDto.class); | ||
|
|
||
| String cid = (mediaFile != null && !mediaFile.isEmpty()) ? ipfsService.uploadFile(mediaFile) : null; | ||
|
|
||
| Capsule saved = capsuleService.createCapsule(dto, cid); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(saved); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public ResponseEntity<Capsule> getCapsuleById(@PathVariable Long id) { | ||
| Capsule capsule = capsuleService.selectCapsuleById(id); | ||
| return (capsule != null) | ||
| ? ResponseEntity.ok(capsule) | ||
| : ResponseEntity.notFound().build(); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public ResponseEntity<Void> deleteCapsule(@PathVariable Long id) { | ||
| capsuleService.deleteCapsule(id); | ||
| return ResponseEntity.noContent().build(); | ||
| } | ||
|
|
||
| @PatchMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | ||
| public ResponseEntity<Void> updateCapsule( | ||
| @PathVariable("id") Long id, | ||
| @RequestPart("data") String data, | ||
| // 수동 데이터 입력 | ||
| // {"capsuleType":"일반","title":"제목","content":"내용","openTime":"2025-07-20T14:03:42.989Z"} | ||
| @RequestPart(value = "media", required = false) MultipartFile mediaFile | ||
| ) throws JsonProcessingException { | ||
|
|
||
| CapsuleUpdateRequestDto dto = objectMapper.readValue(data, CapsuleUpdateRequestDto.class); | ||
|
|
||
| String newCid = null; | ||
| if (mediaFile != null && !mediaFile.isEmpty()) { | ||
| newCid = ipfsService.uploadFile(mediaFile); | ||
| } | ||
|
|
||
| capsuleService.updateCapsule(id, dto, newCid); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.dasom.MemoReal.domain.capsule.dto; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
| import java.util.Date; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| public class CapsuleCreateRequestDto { | ||
|
|
||
|
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. Dto class의 경우
Member
Author
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. 해당 부분 반영하여 코드 수정하겠습니다. 감사합니다 :) |
||
| private String capsuleType; | ||
| private String title; | ||
| private String content; | ||
| private Date openTime; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.dasom.MemoReal.domain.capsule.dto; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| import java.util.Date; | ||
|
|
||
| public class CapsuleSelectResponseDto { | ||
|
|
||
| @Getter | ||
|
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. Getter는 dto column에 두지 말고
Member
Author
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. 앗; 알겠습니다. 바로 반영하겠습니다. 감사합니당 |
||
|
|
||
| private Long capsuleId; | ||
| private String capsuleType; | ||
| private String capsuleTitle; | ||
| private String capsuleContent; | ||
| private Date openTime; | ||
|
|
||
| public CapsuleSelectResponseDto(Long capsuleId) { | ||
| this.capsuleId = capsuleId; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.dasom.MemoReal.domain.capsule.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| import java.util.Date; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @RequiredArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CapsuleUpdateRequestDto { | ||
|
|
||
| // 캡슐 ID는 따로 요청하지 않습니다. * URL을 통해 어떤 ID를 수정할지 알 수 있기 떄문에 | ||
| // private Long capsuleId; | ||
|
|
||
| private String capsuleType; | ||
| private String title; | ||
| private String content; | ||
| private Date openTime; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package com.dasom.MemoReal.domain.capsule.entity; | ||
|
|
||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| import java.util.Date; | ||
|
|
||
| @Entity | ||
| @Getter // 테스트 시 필요 | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class Capsule { | ||
|
|
||
| // 캡슐 아이디 | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long capsuleId; | ||
|
|
||
| // 캡슐 종류 | ||
| private String capsuleType; | ||
|
|
||
| // 제목 | ||
| private String title; | ||
|
|
||
| // 내용 | ||
| private String content; | ||
|
|
||
| // 캡슐 오픈일 | ||
| private Date openTime; | ||
|
|
||
| // IPFS CID 필드 ( 이미지 / 동영상 ) | ||
| private String mediaCid; | ||
|
|
||
|
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. column 마다
Member
Author
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. 엔티티 구조도 @column 사용하여 변경했습니당. 감사합니다 :):) |
||
| // 생성자 (id 제외) | ||
| public Capsule(String capsuleType, String title, String content, Date openTime) { | ||
| this.capsuleType = capsuleType; | ||
| this.title = title; | ||
| this.content = content; | ||
| this.openTime = openTime; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.dasom.MemoReal.domain.capsule.repository; | ||
|
|
||
| import com.dasom.MemoReal.domain.capsule.entity.Capsule; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface CapsuleRepository extends JpaRepository<Capsule, Long> { | ||
|
|
||
| // MySQL과 연동 | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package com.dasom.MemoReal.domain.capsule.service; | ||
|
|
||
| import com.dasom.MemoReal.domain.capsule.dto.CapsuleCreateRequestDto; | ||
| import com.dasom.MemoReal.domain.capsule.dto.CapsuleUpdateRequestDto; | ||
| import com.dasom.MemoReal.domain.capsule.entity.Capsule; | ||
| import com.dasom.MemoReal.domain.capsule.repository.CapsuleRepository; | ||
| import com.dasom.MemoReal.global.exception.CustomException; | ||
| import com.dasom.MemoReal.global.exception.ErrorCode; | ||
| import jakarta.persistence.EntityNotFoundException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class CapsuleService { | ||
|
|
||
| private final CapsuleRepository capsuleRepository; | ||
|
|
||
| public Capsule createCapsule(CapsuleCreateRequestDto dto, String mediaCid) { | ||
| Capsule capsule = new Capsule(); | ||
| capsule.setCapsuleType(dto.getCapsuleType()); | ||
| capsule.setTitle(dto.getTitle()); | ||
| capsule.setContent(dto.getContent()); | ||
| capsule.setOpenTime(dto.getOpenTime()); | ||
| capsule.setMediaCid(mediaCid); | ||
|
|
||
| return capsuleRepository.save(capsule); | ||
| } | ||
|
|
||
|
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. createCapule 함수의 위치가 올바르지 않음
@Getter
Member
Author
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. 이 부분은 아직 완벽하게 이해를 못해서 좀 더 알아보고 수정하여 다시 코멘트 올리겠습니다! |
||
|
|
||
| public Capsule selectCapsuleById(Long id) { | ||
| return capsuleRepository.findById(id).orElse(null); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void deleteCapsule(Long capsuleId) { | ||
| Capsule capsule = capsuleRepository.findById(capsuleId) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.CAPSULE_NOT_FOUND)); | ||
|
|
||
| capsuleRepository.delete(capsule); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void updateCapsule(Long id, CapsuleUpdateRequestDto dto, String newCid) { | ||
| Capsule capsule = capsuleRepository.findById(id) | ||
| .orElseThrow(() -> new EntityNotFoundException("캡슐 없음")); | ||
|
|
||
| capsule.setCapsuleType(dto.getCapsuleType()); | ||
| capsule.setTitle(dto.getTitle()); | ||
| capsule.setContent(dto.getContent()); | ||
| capsule.setOpenTime(dto.getOpenTime()); | ||
|
|
||
| if (newCid != null) { | ||
| capsule.setMediaCid(newCid); // 새로운 이미지로 대체 | ||
| } | ||
|
|
||
| capsuleRepository.save(capsule); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.dasom.MemoReal.domain.ipfs.config; | ||
|
|
||
| import io.ipfs.api.IPFS; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| public class IpfsConfig { | ||
|
|
||
| @Bean | ||
| public IPFS ipfs() { | ||
| // 로컬 IPFS 노드 (기본 포트 5001) | ||
| return new IPFS("/ip4/127.0.0.1/tcp/5001"); | ||
| } | ||
| } |
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.
@PostMapping
public ResponseEntity createCapsule(@ModelAttribute InputDto dto);
InputDto {
MultipartFile meiaFile;
String data;
}
@ModelAttribute annotion을 사용해 String과 MultipartFile 동시에 받을 수 있음
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.
@ModelAttribute 라는 어노테이션 사용하여 컨트롤러 코드 구조 변경해봤습니다 감사합니당.
구조 변경하면서 Dto도 기존에는 기능별로 나뉘어져 있었는데 구조 합쳐서 다시 만들었습니다
이런식으로 변경했는데, 어노테이션이 겹치는 부분이 너무 많은데 괜찮은거 맞을까용;;;
리뷰 감사합니다. 배워갑니당 >0<