diff --git a/src/main/java/com/gongjakso/server/domain/portfolio/controller/PortfolioController.java b/src/main/java/com/gongjakso/server/domain/portfolio/controller/PortfolioController.java index 3b1d1f57..b30c1e2f 100644 --- a/src/main/java/com/gongjakso/server/domain/portfolio/controller/PortfolioController.java +++ b/src/main/java/com/gongjakso/server/domain/portfolio/controller/PortfolioController.java @@ -1,6 +1,7 @@ package com.gongjakso.server.domain.portfolio.controller; import com.gongjakso.server.domain.portfolio.dto.request.PortfolioReq; +import com.gongjakso.server.domain.portfolio.dto.response.ExistPortfolioRes; import com.gongjakso.server.domain.portfolio.dto.response.PortfolioRes; import com.gongjakso.server.domain.portfolio.dto.response.SimplePortfolioRes; import com.gongjakso.server.domain.portfolio.service.PortfolioService; @@ -11,14 +12,8 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -66,4 +61,33 @@ public ApplicationResponse deletePortfolio(@AuthenticationPrincipal Princi public ApplicationResponse> getMyPortfolios(@AuthenticationPrincipal PrincipalDetails principalDetails) { return ApplicationResponse.ok(portfolioService.getMyPortfolios(principalDetails.getMember())); } + + @Operation(description = "포트폴리오 파일 및 링크 업로드 API") + @PostMapping("/exist-protfolio") + public ApplicationResponse updateExistPortfolio(@AuthenticationPrincipal PrincipalDetails principalDetails, @RequestPart(required = false,name = "file") MultipartFile file, @RequestPart(required = false,name = "notionUri") String notionUri){ + portfolioService.saveExistPortfolio(principalDetails.getMember(),file,notionUri); + return ApplicationResponse.ok(); + } + + @Operation(description = "포트폴리오 파일 및 링크 업로드 삭제 API") + @DeleteMapping("/exist-protfolio/{portfolio_id}") + public ApplicationResponse deleteExistPortfolio(@AuthenticationPrincipal PrincipalDetails principalDetails,@PathVariable("portfolio_id") Long portfolioId){ + portfolioService.deleteExistPortfolio(principalDetails.getMember(),portfolioId); + return ApplicationResponse.ok(); + } + + @Operation(description = "포트폴리오 파일 및 링크 업로드 업데이트 API") + @PatchMapping("/exist-protfolio/{portfolio_id}") + public ApplicationResponse updateExistPortfolio(@AuthenticationPrincipal PrincipalDetails principalDetails,@PathVariable("portfolio_id") Long portfolioId, @RequestPart(required = false,name = "file") MultipartFile file, @RequestPart(required = false,name = "notionUri") String notionUri){ + portfolioService.updateExistPortfolio(principalDetails.getMember(),portfolioId,file,notionUri); + return ApplicationResponse.ok(); + } + + @Operation(description = "포트폴리오 파일 및 링크 업로드 가져오기 API") + @GetMapping("/exist-protfolio/{portfolio_id}") + public ApplicationResponse findExistPortfolio(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable("portfolio_id") Long portfolioId){ + return ApplicationResponse.ok(portfolioService.findExistPorfolio(principalDetails.getMember(),portfolioId)); + } + + } diff --git a/src/main/java/com/gongjakso/server/domain/portfolio/dto/response/ExistPortfolioRes.java b/src/main/java/com/gongjakso/server/domain/portfolio/dto/response/ExistPortfolioRes.java new file mode 100644 index 00000000..5143d332 --- /dev/null +++ b/src/main/java/com/gongjakso/server/domain/portfolio/dto/response/ExistPortfolioRes.java @@ -0,0 +1,15 @@ +package com.gongjakso.server.domain.portfolio.dto.response; + +import com.gongjakso.server.domain.portfolio.entity.Portfolio; + +public record ExistPortfolioRes( + String fileUri, + String notionUri +) { + public static ExistPortfolioRes of(Portfolio portfolio) { + return new ExistPortfolioRes( + portfolio.getFileUri(), + portfolio.getNotionUri() + ); + } +} diff --git a/src/main/java/com/gongjakso/server/domain/portfolio/entity/Portfolio.java b/src/main/java/com/gongjakso/server/domain/portfolio/entity/Portfolio.java index ff563f67..a7682519 100644 --- a/src/main/java/com/gongjakso/server/domain/portfolio/entity/Portfolio.java +++ b/src/main/java/com/gongjakso/server/domain/portfolio/entity/Portfolio.java @@ -1,5 +1,6 @@ package com.gongjakso.server.domain.portfolio.entity; +import com.gongjakso.server.domain.contest.dto.request.UpdateContestDto; import com.gongjakso.server.domain.member.entity.Member; import com.gongjakso.server.domain.portfolio.vo.PortfolioData; import com.gongjakso.server.global.common.BaseTimeEntity; @@ -12,6 +13,7 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.SQLDelete; import org.hibernate.type.SqlTypes; +import org.springframework.data.redis.connection.ReactiveGeoCommands; @Getter @Entity @@ -36,13 +38,32 @@ public class Portfolio extends BaseTimeEntity { @JdbcTypeCode(SqlTypes.JSON) private PortfolioData portfolioData; - @Builder + @Column(name = "file_uri",columnDefinition = "text") + private String fileUri; + + @Column(name = "notion_uri",columnDefinition = "text") + private String notionUri; + + @Builder(builderMethodName = "portfolioBuilder") public Portfolio(Member member, String portfolioName, PortfolioData portfolioData) { this.member = member; this.portfolioName = portfolioName; this.portfolioData = portfolioData; } + @Builder(builderMethodName = "existPortfolioBuilder") + public Portfolio(Member member, String portfolioName, String fileUri, String notionUri){ + this.member = member; + this.portfolioName = portfolioName; + this.fileUri = fileUri; + this.notionUri = notionUri; + } + public void updatePortfolioUri(Portfolio portfolio, String fileUri, String notionUri) { + this.fileUri = (fileUri == null) ? this.fileUri : fileUri; + this.notionUri = (notionUri == null) ? this.notionUri : notionUri; + } + + public void updateName(String updatedName) { this.portfolioName = updatedName; } diff --git a/src/main/java/com/gongjakso/server/domain/portfolio/repository/PortfolioRepository.java b/src/main/java/com/gongjakso/server/domain/portfolio/repository/PortfolioRepository.java index cb0cc04b..b49d6399 100644 --- a/src/main/java/com/gongjakso/server/domain/portfolio/repository/PortfolioRepository.java +++ b/src/main/java/com/gongjakso/server/domain/portfolio/repository/PortfolioRepository.java @@ -1,11 +1,18 @@ package com.gongjakso.server.domain.portfolio.repository; +import com.gongjakso.server.domain.member.entity.Member; import com.gongjakso.server.domain.portfolio.entity.Portfolio; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface PortfolioRepository extends JpaRepository, PortfolioRepositoryCustom{ Optional findByIdAndDeletedAtIsNull(Long portfolioId); - long countByDeletedAtIsNull(); + @Query("SELECT EXISTS (" + + "SELECT 1 FROM Portfolio po " + + "WHERE po.member = :member AND (po.fileUri IS NOT NULL OR po.notionUri IS NOT NULL) AND po.deletedAt IS NULL )") + Boolean existsExistPortfolioByMember(@Param("member") Member member); + Optional findPortfolioById(Long portfolioId); } diff --git a/src/main/java/com/gongjakso/server/domain/portfolio/service/PortfolioService.java b/src/main/java/com/gongjakso/server/domain/portfolio/service/PortfolioService.java index f94554fd..6e19868e 100644 --- a/src/main/java/com/gongjakso/server/domain/portfolio/service/PortfolioService.java +++ b/src/main/java/com/gongjakso/server/domain/portfolio/service/PortfolioService.java @@ -2,6 +2,7 @@ import com.gongjakso.server.domain.member.entity.Member; import com.gongjakso.server.domain.portfolio.dto.request.PortfolioReq; +import com.gongjakso.server.domain.portfolio.dto.response.ExistPortfolioRes; import com.gongjakso.server.domain.portfolio.dto.response.PortfolioRes; import com.gongjakso.server.domain.portfolio.dto.response.SimplePortfolioRes; import com.gongjakso.server.domain.portfolio.entity.Portfolio; @@ -10,9 +11,12 @@ import com.gongjakso.server.global.exception.ApplicationException; import com.gongjakso.server.global.exception.ErrorCode; import java.util.List; + +import com.gongjakso.server.global.util.s3.S3Client; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @Service @Transactional(readOnly = true) @@ -20,6 +24,9 @@ public class PortfolioService { private final PortfolioRepository portfolioRepository; + private final S3Client s3Client; + private final String S3_PORTFOLIO_DIR_NAME = "portfolio"; + // PortfolioName 생성 로직을 분리 private String generatePortfolioName(String portfolioName) { @@ -104,11 +111,7 @@ public PortfolioRes registerPortfolio(Member member, PortfolioReq portfolioReq) String portfolioName = generatePortfolioName(portfolioReq.portfolioName()); PortfolioData portfolioData = convertToPortfolioData(portfolioReq); - Portfolio portfolio = Portfolio.builder() - .member(member) - .portfolioName(portfolioName) - .portfolioData(portfolioData) - .build(); + Portfolio portfolio = new Portfolio(member,portfolioName,portfolioData); Portfolio savedPortfolio = portfolioRepository.save(portfolio); @@ -165,4 +168,66 @@ public List getMyPortfolios(Member member) { .map(SimplePortfolioRes::of) .toList(); } + + @Transactional + public void saveExistPortfolio(Member member, MultipartFile file, String notionUri){ + //등록된 파일이나 노션 링크 있는지 확인 + //Validation + Boolean isExist = portfolioRepository.existsExistPortfolioByMember(member); + if (isExist){ + throw new ApplicationException(ErrorCode.ALREADY_EXIST_EXCEPTION); + } + if(file.isEmpty() && notionUri.isEmpty()){ + throw new ApplicationException(ErrorCode.PORTFOLIO_SAVE_FAILED_EXCEPTION); + } + //Business + String s3Url = null; + if (file != null && !file.isEmpty()) { + s3Url = s3Client.upload(file, S3_PORTFOLIO_DIR_NAME); + } + Portfolio portfolio = new Portfolio(member,generatePortfolioName(null),s3Url,notionUri); + portfolioRepository.save(portfolio); + } + + @Transactional + public void deleteExistPortfolio(Member member, Long id){ + Portfolio portfolio = portfolioRepository.findById(id).orElseThrow(()-> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + if(!member.getId().equals(portfolio.getMember().getId())){ + throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); + } + if(portfolio.getFileUri() != null&& !portfolio.getFileUri().isEmpty()){ + s3Client.delete(portfolio.getFileUri()); + } + portfolioRepository.delete(portfolio); + } + + @Transactional + public void updateExistPortfolio(Member member, Long id, MultipartFile file, String notionUri){ + //등록된 파일이나 노션 링크 있는지 확인 + //Validation + Portfolio portfolio = portfolioRepository.findById(id).orElseThrow(()-> new ApplicationException(ErrorCode.PORTFOLIO_NOT_FOUND_EXCEPTION)); + if(!member.getId().equals(portfolio.getMember().getId())){ + throw new ApplicationException(ErrorCode.UNAUTHORIZED_EXCEPTION); + } + //Business + String s3Url = null; + if (file != null && !file.isEmpty()) { + if(portfolio.getFileUri()!=null && !portfolio.getFileUri().isEmpty()){ + s3Client.delete(portfolio.getFileUri()); + } + s3Url = s3Client.upload(file, S3_PORTFOLIO_DIR_NAME); + } + portfolio.updatePortfolioUri(portfolio,s3Url,notionUri); + portfolioRepository.save(portfolio); + } + + public ExistPortfolioRes findExistPorfolio(Member member, Long id){ + //Validation + Portfolio portfolio = portfolioRepository.findById(id).orElseThrow(()-> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + if (!portfolio.getMember().getId().equals(member.getId())) { + throw new ApplicationException(ErrorCode.FORBIDDEN_EXCEPTION); + } + return new ExistPortfolioRes(portfolio.getFileUri(),portfolio.getNotionUri()); + } + } \ No newline at end of file