Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .gradle/8.13/checksums/checksums.lock
Binary file not shown.
Binary file modified .gradle/8.13/checksums/md5-checksums.bin
Binary file not shown.
Binary file modified .gradle/8.13/checksums/sha1-checksums.bin
Binary file not shown.
Binary file modified .gradle/8.13/executionHistory/executionHistory.lock
Binary file not shown.
Binary file modified .gradle/8.13/fileHashes/fileHashes.bin
Binary file not shown.
Binary file modified .gradle/8.13/fileHashes/fileHashes.lock
Binary file not shown.
Binary file modified .gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ dependencies {
//implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'io.minio:minio:8.5.7'
//testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package study.goorm.domain.cloth.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import study.goorm.domain.cloth.application.ClothService;
import study.goorm.domain.cloth.dto.ClothRequestDTO;
import study.goorm.domain.cloth.dto.ClothResponseDTO;
import study.goorm.domain.model.enums.ClothSort;
import study.goorm.domain.model.exception.annotation.CheckPage;
import study.goorm.domain.model.exception.annotation.CheckPageSize;
import study.goorm.global.common.response.BaseResponse;
import study.goorm.global.error.code.status.SuccessStatus;

@RestController
@RequiredArgsConstructor
@RequestMapping("/cloth") //원래 cloth-였는데 cloth로 바꿨음
@Validated
public class ClothRestController {

private final ClothService clothService;

@GetMapping("/{cloth-id}/edit-view")
@Operation(summary = "특정 Cloth에 대한 정보를 수정용으로 조회하는 API",description = "Path Variable로 clothId를 던져주세요.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "CLOTH_200", description = "OK, 성공적으로 조회되었습니다."),
})
public BaseResponse<ClothResponseDTO.ClothEditViewResult> getClothEditView(
@PathVariable(name = "cloth-id") Long clothId
){
ClothResponseDTO.ClothEditViewResult result=clothService.getClothEditView(clothId);
return BaseResponse.onSuccess(SuccessStatus.CLOTH_VIEW_SUCCESS,result);
}



@GetMapping("/closet-view")
@Operation(summary = "유저의 옷장을 조회하는 API",description = "query string으로 sort,page,size를 넘겨주세요.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "CLOTH_200",description = "OK, 성공적으로 조회되었습니다.")
})
@Parameters({
@Parameter(name = "clokey-id", description = "클로키 유저의 clokey id, query string 입니다."),
@Parameter(name = "sort", description = "정렬(Sort) ENUM 값 { WEAR, NOT_WEAR, LATEST, OLDEST }, query string 입니다."),
@Parameter(name = "page", description = "페이지 값, query string 입니다."),
@Parameter(name = "size", description = "페이지에 표시할 요소 개수 값, query string 입니다.")
})
public BaseResponse<ClothResponseDTO.MemberClosetResult>getMemberCloset(
@RequestParam(value="clokey-id") String clokeyId,
@RequestParam ClothSort sort,
@RequestParam @CheckPage int page,
@RequestParam @CheckPageSize int size
){

ClothResponseDTO.MemberClosetResult result=clothService.getMemberCloset(clokeyId,sort,page-1,size);
return BaseResponse.onSuccess(SuccessStatus.CLOTH_VIEW_SUCCESS,result);

}



@PostMapping(value = "",consumes= MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "새로운 옷을 생성하는 API", description = "request body에 ClothCreateRequest 형식의 데이터를 전달해주세요.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "CLOTH_201",description = "CREATED,성공적으로 생성되었습니다."),

})
public BaseResponse<ClothResponseDTO.ClothCreateResult> createCloth(
@RequestPart("clothCreateRequest") @Valid ClothRequestDTO.ClothCreateRequest clothCreateRequest,
@RequestPart("imageFile") MultipartFile imageFile
){
ClothResponseDTO.ClothCreateResult result=clothService.createCloth(clothCreateRequest,imageFile);
return BaseResponse.onSuccess(SuccessStatus.CLOTH_CREATED,result);
}

@DeleteMapping("/{cloth-id}")
@Operation(summary = "특정 옷을 삭제하는 API",description = "path variable로 cloth_id를 넘겨주세요.")
@Parameters({@Parameter(name = "cloth-id",description = "옷의 id,path variable입니다.")})
public BaseResponse<Void> deleteCloth (
@PathVariable(value="cloth-id") Long clothId
){
clothService.deleteCloth(clothId);
return BaseResponse.onSuccess(SuccessStatus.CLOTH_DELETED,null);
}



}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package study.goorm.domain.cloth.application;

import study.goorm.domain.cloth.domain.entity.Cloth;

import java.util.Map;

public interface ClothImageQueryService {
Map<Long,String> getFirstImageUrlMap(Iterable<Cloth> clothes);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package study.goorm.domain.cloth.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import study.goorm.domain.cloth.domain.entity.Cloth;
import study.goorm.domain.cloth.domain.entity.ClothImage;
import study.goorm.domain.cloth.domain.repository.ClothImageRepository;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service
@RequiredArgsConstructor
public class ClothImageQueryServiceImpl implements ClothImageQueryService {
private final ClothImageRepository clothImageRepository;

@Override
public Map<Long,String> getFirstImageUrlMap(Iterable<Cloth> clothes){
List<Long> clothIds= StreamSupport.stream(clothes.spliterator(),false)
.map(Cloth::getId)
.toList();

List<ClothImage> firstImages=clothImageRepository.findFirstImagesByClothIds(clothIds);

return firstImages.stream()
.collect(Collectors.toMap(
image->image.getCloth().getId(),
ClothImage::getImageUrl
));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package study.goorm.domain.cloth.application;

import org.springframework.web.multipart.MultipartFile;
import study.goorm.domain.cloth.dto.ClothRequestDTO;
import study.goorm.domain.cloth.dto.ClothResponseDTO;
import study.goorm.domain.model.enums.ClothSort;

public interface ClothService {
ClothResponseDTO.ClothEditViewResult getClothEditView(Long clothId);
ClothResponseDTO.MemberClosetResult getMemberCloset(String clokeyId, ClothSort sort,int page,int size);
ClothResponseDTO.ClothCreateResult createCloth(ClothRequestDTO.ClothCreateRequest clothCreateResult ,MultipartFile image);
void deleteCloth(Long clothId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package study.goorm.domain.cloth.application;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import study.goorm.domain.cloth.converter.ClothConverter;
import study.goorm.domain.cloth.domain.entity.Category;
import study.goorm.domain.cloth.domain.entity.Cloth;
import study.goorm.domain.cloth.domain.entity.ClothImage;
import study.goorm.domain.cloth.domain.repository.CategoryRepository;
import study.goorm.domain.cloth.domain.repository.ClothImageRepository;
import study.goorm.domain.cloth.domain.repository.ClothRepository;
import study.goorm.domain.cloth.dto.ClothRequestDTO;
import study.goorm.domain.cloth.dto.ClothResponseDTO;
import study.goorm.domain.cloth.exception.ClothException;
import study.goorm.domain.folder.domain.repository.ClothFolderRepository;
import study.goorm.domain.history.domain.repository.HistoryClothRepository;
import study.goorm.domain.member.domain.entity.Member;
import study.goorm.domain.member.domain.exception.MemberException;
import study.goorm.domain.member.domain.repository.MemberRepository;
import study.goorm.domain.model.enums.ClothSort;
import study.goorm.global.error.code.status.ErrorStatus;

import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class ClothServiceImpl implements ClothService {

private final ClothRepository clothRepository;
private final ClothImageRepository clothImageRepository;
private final MemberRepository memberRepository;
private final ClothImageQueryService clothImageQueryService;
private final CategoryRepository categoryRepository;
private final ClothFolderRepository clothFolderRepository;
private final HistoryClothRepository historyClothRepository;

@Override
@Transactional(readOnly = true)
public ClothResponseDTO.ClothEditViewResult getClothEditView(Long clothId){

Cloth cloth = clothRepository.findById(clothId)
.orElseThrow(() -> new ClothException(ErrorStatus.NO_SUCH_CLOTH));

List<ClothImage> clothImageUrls=clothImageRepository.findAllByCloth(cloth);

String firstImageUrl=clothImageUrls.stream()
.findFirst()
.map(ClothImage::getImageUrl)
.orElseThrow(()->new ClothException(ErrorStatus.NO_ClOTH_IMAGE));



return ClothConverter.toClothEditViewResult(cloth,firstImageUrl);
}

@Override
@Transactional(readOnly = true)
public ClothResponseDTO.MemberClosetResult getMemberCloset(String clokeyId, ClothSort sort, int page, int size){
Member member=memberRepository.findByClokeyId(clokeyId)
.orElseThrow(()->new MemberException(ErrorStatus.NO_SUCH_MEMBER));

PageRequest pageRequest=PageRequest .of(page,size);
Page<Cloth> clothes;

if(sort.equals(ClothSort.LATEST)){
clothes=clothRepository.findByMemberOrderByCreatedAtDesc(member,pageRequest);
}
else if (sort.equals(ClothSort.OLDEST)){
clothes=clothRepository.findByMemberOrderByCreatedAtAsc(member,pageRequest);

}
else if(sort.equals(ClothSort.WEAR)){
clothes=clothRepository.findByMemberOrderByWearNumDesc(member,pageRequest);

}
else{
clothes=clothRepository.findByMemberOrderByWearNumAsc(member,pageRequest);
}
Map<Long,String> firstImagesOfCloth=clothImageQueryService.getFirstImageUrlMap(clothes);

return ClothConverter.toMemberClosetResult(member,firstImagesOfCloth,clothes);
}

@Override
@Transactional
public ClothResponseDTO.ClothCreateResult createCloth(ClothRequestDTO.ClothCreateRequest clothCreateResult, MultipartFile image){
Member member=memberRepository.findById(clothCreateResult.getMemberId())
.orElseThrow(()->new MemberException(ErrorStatus.NO_SUCH_MEMBER));

Category category=categoryRepository.findById(clothCreateResult.getCategoryId())
.orElseThrow(()->new ClothException(ErrorStatus.NO_SUCH_CATEGORY));

Cloth newCloth=Cloth.builder()
.name(clothCreateResult.getName())
.wearNum(0)
.season(clothCreateResult.getSeasons())
.tempUpperBound(clothCreateResult.getTempUpperBound())
.tempLowerBound(clothCreateResult.getTempLowerBound())
.thicknessLevel(clothCreateResult.getThicknessLevel())
.clothUrl(clothCreateResult.getClothUrl())
.brand(clothCreateResult.getBrand())
.category(category)
.member(member)
.build();

clothRepository.save(newCloth);

ClothImage newClothImage=ClothImage.builder()
.cloth(newCloth)
.imageUrl("아직 S3를 구현하지 않아서 url이 없어용")
.build();
clothImageRepository.save(newClothImage);

return ClothConverter.toClothCreateResult(newCloth);


}

@Override
@Transactional
public void deleteCloth(Long clothId){
Cloth cloth=clothRepository.findById(clothId)
.orElseThrow(()->new ClothException(ErrorStatus.NO_SUCH_CLOTH));
//매핑 테이블 삭제
clothImageRepository.deleteAllByCloth(cloth);
clothFolderRepository.deleteAllByCloth(cloth);
historyClothRepository.deleteAllByCloth(cloth);

//최종 옷 삭제
clothRepository.delete(cloth);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package study.goorm.domain.cloth.converter;


import org.springframework.data.domain.Page;
import study.goorm.domain.cloth.domain.entity.Cloth;
import study.goorm.domain.cloth.dto.ClothResponseDTO;
import study.goorm.domain.member.domain.entity.Member;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


public class ClothConverter {
public static ClothResponseDTO.ClothEditViewResult toClothEditViewResult(Cloth cloth, String clothImageUrl){
return ClothResponseDTO.ClothEditViewResult.builder()
.id(cloth.getId())
.brand(cloth.getBrand())
.categoryId(cloth.getCategory().getId())
.clothUrl(cloth.getClothUrl())
.imageUrl(clothImageUrl)
.name(cloth.getName())
.seasons(cloth.getSeason())
.tempLowerBound(cloth.getTempLowerBound())
.tempUpperBound(cloth.getTempUpperBound())
.thicknessLevel(cloth.getThicknessLevel())
.build();

}
public static ClothResponseDTO.MemberClosetResult toMemberClosetResult(Member member, Map<Long,String> firstImagesOfCloth, Page<Cloth> clothes){
return ClothResponseDTO.MemberClosetResult.builder()
.nickName(member.getNickname())
.clothPreviewListResult(toClothPreviewListResult(firstImagesOfCloth,clothes))
.build();
}
private static ClothResponseDTO.ClothPreviewListResult toClothPreviewListResult(Map<Long,String> firstImagesOfCloth,Page<Cloth> clothes){
return ClothResponseDTO.ClothPreviewListResult.builder()
.clothPreviews(toClothPreview(firstImagesOfCloth,clothes))
.isFirst(clothes.isFirst())
.isLast(clothes.isLast())
.totalElements(clothes.getTotalElements())
.totalPage(clothes.getTotalPages())
.build();
}
private static List<ClothResponseDTO.ClothPreview> toClothPreview(Map<Long,String> firstImagesOfCloth, Page<Cloth> clothes){
return clothes.stream()
.map(cloth->ClothResponseDTO.ClothPreview.builder()
.id(cloth.getId())
.name(cloth.getName())
.wearNum(cloth.getWearNum())
.imageUrl(firstImagesOfCloth.get(cloth.getId()))
.build())
.collect(Collectors.toList());
}

public static ClothResponseDTO.ClothCreateResult toClothCreateResult(Cloth cloth){
return ClothResponseDTO.ClothCreateResult.builder()
.id(cloth.getId())
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ public class Cloth extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;

//hyowon
public void increaseWearCount(){
this.wearNum++;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.springframework.data.jpa.repository.JpaRepository;
import study.goorm.domain.cloth.domain.entity.Category;
import study.goorm.domain.cloth.domain.entity.Cloth;

public interface CategoryRepository extends JpaRepository<Category, Long> {

}
Loading