Skip to content
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

✨ 반려동물 수정/삭제 API 및 사용자 설정 이름 반환 API #121

Merged
merged 5 commits into from
Mar 2, 2024
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
- name: docker image build & push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
cat ${{ secrets.IOS_FIREBASE_ADMINSDK }} > fitapet-app-external-api/src/main/resources/fitapet-ios-firebase-adminsdk-ethnn-6ec10fe329.json
docker build -t ${{ secrets.DOCKER_NICKNAME }}/fitapet:latest .
docker push ${{ secrets.DOCKER_NICKNAME }}/fitapet:latest

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kr.co.fitapet.api.apis.notification.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "알림 API")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2/notifications")
public class NotificationApi {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kr.co.fitapet.api.apis.notification.usecase;

import kr.co.fitapet.common.annotation.UseCase;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@UseCase
@RequiredArgsConstructor
public class NotificationUseCase {
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

@Tag(name = "반려동물 관리 API")
@RestController
@RequestMapping("/api/v2/users/{user_id}/pets")
@RequestMapping("/api/v2/pets")
@RequiredArgsConstructor
@Slf4j
public class PetApi {
Expand All @@ -44,34 +44,59 @@ public class PetApi {
@PostMapping("")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> savePet(@RequestBody @Valid PetSaveReq req, @AuthenticationPrincipal CustomUserDetails user) {
petUseCase.savePet(req.toPetEntity(), user.getUserId());
petUseCase.savePet(req.toEntity(), user.getUserId());

return ResponseEntity.ok(SuccessResponse.noContent());
}

@Operation(summary = "관리 중인 반려동물 리스트 조회")
@GetMapping("")
@PreAuthorize("isAuthenticated() and #userId == principal.userId")
public ResponseEntity<?> getPets(@PathVariable("user_id") Long userId) {
PetInfoRes pets = petUseCase.getPets(userId);
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> getPets(@AuthenticationPrincipal CustomUserDetails user) {
PetInfoRes pets = petUseCase.findPets(user.getUserId());
return ResponseEntity.ok(SuccessResponse.from("pets", pets.getPets()));
}

@Operation(summary = "관리 중인 반려동물 목록 조회")
@Parameter(name = "user_id", description = "조회할 유저 ID", required = true)
@Operation(summary = "관리 중인 반려동물 요약 목록 조회")
@GetMapping("/summary")
@PreAuthorize("isAuthenticated() and #userId == principal.userId")
public ResponseEntity<?> findPets(@PathVariable("user_id") Long userId) {
List<?> pets = petUseCase.findPetsSummaryByUserId(userId).getPets();
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> findPets(@AuthenticationPrincipal CustomUserDetails user) {
List<?> pets = petUseCase.findPetsSummaryByUserId(user.getUserId()).getPets();
return ResponseEntity.ok(SuccessResponse.from("pets", pets));
}

@Operation(summary = "반려동물 정보 조회")
@GetMapping("/{pet_id}")
@PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId)")
public ResponseEntity<?> getPet(@PathVariable("pet_id") Long petId) {
return ResponseEntity.ok(SuccessResponse.from("pet", petUseCase.findPet(petId)));
}

@Operation(summary = "반려동물 케어 카테고리 유효성 검사")
@Parameter(name = "user_id", description = "조회할 유저 ID", in = ParameterIn.PATH, required = true)
@PostMapping("/categories-check")
@PreAuthorize("isAuthenticated() and #userId == principal.userId")
public ResponseEntity<?> checkCategoryExist(@PathVariable("user_id") Long userId, @RequestBody @Valid CareCategoryInfo.CareCategoryExistRequest request) {
List<?> result = petUseCase.checkCategoryExist(userId, request.categoryName(), request.pets());
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> checkCategoryExist(@AuthenticationPrincipal CustomUserDetails user, @RequestBody @Valid CareCategoryInfo.CareCategoryExistRequest request) {
List<?> result = petUseCase.checkCategoryExist(user.getUserId(), request.categoryName(), request.pets());
return ResponseEntity.ok(SuccessResponse.from("categories", result));
}

@Operation(summary = "반려동물 정보 수정")
@Parameter(name = "pet_id", description = "수정할 반려동물 ID", in = ParameterIn.PATH, required = true)
@PutMapping("/{pet_id}")
@PreAuthorize("isAuthenticated() and @managerAuthorize.isMaster(principal.userId, #petId)")
public ResponseEntity<?> updatePet(@PathVariable("pet_id") Long petId, @RequestBody @Valid PetSaveReq req) {
petUseCase.updatePet(petId, req);
return ResponseEntity.ok(SuccessResponse.noContent());
}

@Operation(summary = "반려동물 삭제")
@Parameter(name = "pet_id", description = "삭제할 반려동물 ID", in = ParameterIn.PATH, required = true)
@DeleteMapping("/{pet_id}")
@PreAuthorize("isAuthenticated() and @managerAuthorize.isMaster(principal.userId, #petId)")
public ResponseEntity<?> deletePet(@PathVariable("pet_id") Long petId, @AuthenticationPrincipal CustomUserDetails user) {
petUseCase.deletePet(petId, user.getUserId());
return ResponseEntity.ok(SuccessResponse.noContent());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kr.co.fitapet.api.apis.pet.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import kr.co.fitapet.domain.domains.pet.domain.Pet;
import kr.co.fitapet.domain.domains.pet.type.GenderType;

import java.time.LocalDate;
import java.util.Objects;

public record PetDetailRes(
Long id,
String petName,
String petProfileImage,
GenderType gender,
Boolean neutered,
@JsonSerialize(using = LocalDateSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
LocalDate birthdate,
String species,
String feed
) {
public static PetDetailRes from(Pet pet) {
return new PetDetailRes(
pet.getId(),
pet.getPetName(),
Objects.toString(pet.getPetProfileImg(), ""),
pet.getGender(),
pet.isNeutered(),
pet.getBirthdate(),
pet.getSpecies(),
Objects.toString(pet.getFeed(), "")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kr.co.fitapet.api.apis.pet.mapper;

import kr.co.fitapet.common.annotation.Mapper;
import kr.co.fitapet.domain.domains.memo.service.MemoSearchService;
import kr.co.fitapet.domain.domains.pet.domain.Pet;
import kr.co.fitapet.domain.domains.pet.service.PetSearchService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Mapper
@RequiredArgsConstructor
public class PetImageSearchMapper {
private final PetSearchService petSearchService;
private final MemoSearchService memoSearchService;

/**
* 반려동물 프로필 이미지와 메모에 등록된 모든 이미지 조회
*/
@Transactional(readOnly = true)
public List<String> findPetProfileImageAndMemoImageUrls(Long petId) {
List<String> images = new ArrayList<>();

Pet pet = petSearchService.findPetById(petId);
if (pet.getPetProfileImg() != null)
images.add(pet.getPetProfileImg());
List<Long> memoIds = memoSearchService.findMemoIdsByPetId(pet.getId());
List<String> memoImageUrls = memoSearchService.findMemoImageUrlsByMemoIds(memoIds);
images.addAll(memoImageUrls);

return images;
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
package kr.co.fitapet.api.apis.pet.usecase;

import kr.co.fitapet.api.apis.pet.dto.PetDetailRes;
import kr.co.fitapet.api.apis.pet.helper.PetCareHelper;
import kr.co.fitapet.api.apis.pet.mapper.PetImageSearchMapper;
import kr.co.fitapet.api.apis.pet.mapper.PetManagerMapper;
import kr.co.fitapet.api.common.security.jwt.exception.AuthErrorCode;
import kr.co.fitapet.api.common.security.jwt.exception.AuthErrorException;
import kr.co.fitapet.common.annotation.UseCase;
import kr.co.fitapet.domain.domains.care.service.CareSearchService;
import kr.co.fitapet.domain.domains.memo.domain.MemoCategory;
import kr.co.fitapet.domain.domains.pet.domain.Pet;
import kr.co.fitapet.domain.domains.pet.dto.PetInfoRes;
import kr.co.fitapet.domain.domains.pet.dto.PetSaveReq;
import kr.co.fitapet.domain.domains.pet.service.PetDeleteService;
import kr.co.fitapet.domain.domains.pet.service.PetSaveService;
import kr.co.fitapet.domain.domains.pet.service.PetSearchService;
import kr.co.fitapet.infra.common.event.ObjectStorageImageDeleteEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@UseCase
@Slf4j
@RequiredArgsConstructor
public class PetUseCase {
private final PetManagerMapper petManagerMapper;
private final PetImageSearchMapper petImageSearchMapper;

private final PetSearchService petSearchService;
private final PetSaveService petSaveService;
private final PetDeleteService petDeleteService;
private final PetCareHelper petCareHelper;

private final ApplicationEventPublisher eventPublisher;

@Transactional
public void savePet(Pet pet, Long memberId) {
pet = petSaveService.savePet(pet);
Expand All @@ -45,8 +60,31 @@ public List<?> checkCategoryExist(Long userId, String categoryName, List<Long> p
}

@Transactional(readOnly = true)
public PetInfoRes getPets(Long userId) {
public PetInfoRes findPets(Long userId) {
List<Pet> pets = petManagerMapper.findAllManagerByMemberId(userId);
return PetInfoRes.ofPetInfo(pets);
}

@Transactional(readOnly = true)
public PetDetailRes findPet(Long petId) {
Pet pet = petSearchService.findPetById(petId);
return PetDetailRes.from(pet);
}

@Transactional
public void updatePet(Long petId, PetSaveReq req) {
Pet pet = petSearchService.findPetById(petId);
pet.updatePet(req.toEntity());
petSaveService.savePet(pet);
}

@Transactional
@CacheEvict(value = "master", key = "#masterId + '@' + #petId", cacheManager = "managerCacheManager")
public void deletePet(Long petId, Long masterId) {
log.info("deletePet: {}", petId);
List<String> deleteImageUrls = petImageSearchMapper.findPetProfileImageAndMemoImageUrls(petId);
log.info("deleteImageUrls: {}", deleteImageUrls);
petDeleteService.deletePet(petId);
eventPublisher.publishEvent(ObjectStorageImageDeleteEvent.valueOf(deleteImageUrls));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ public ResponseEntity<?> getProfile(@PathVariable("id") Long id) {
return ResponseEntity.ok(SuccessResponse.from(member));
}

@Operation(summary = "사용자 설정 이름 조회")
@Parameter(name = "id", description = "조회할 프로필 ID", in = ParameterIn.PATH, required = true)
@GetMapping("/{id}/name")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> getUserName(@PathVariable("id") Long id) {
return ResponseEntity.ok(SuccessResponse.from("name", memberAccountUseCase.findUserName(id)));
}

@Operation(summary = "디바이스 토큰 등록")
@PostMapping("/{id}/device-token")
@PreAuthorize("isAuthenticated() and #id == principal.userId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public AccountProfileRes getProfile(Long userId) {
return AccountProfileRes.from(member);
}

@Transactional(readOnly = true)
public String findUserName(Long userId) {
return memberSearchService.findById(userId).getName();
}

@Transactional
public void registerDeviceToken(Long memberId, DeviceTokenReq req) {
Member member = memberSearchService.findById(memberId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -17,7 +18,7 @@
public class NotificationTestController {
private final NotificationTestService notificationTestService;

@RequestMapping("/push")
@GetMapping("/push")
@PreAuthorize("isAuthenticated()")
public void push(@AuthenticationPrincipal CustomUserDetails user) {
notificationTestService.push(user.getUserId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ public Map<Long, LocalDateTime> findAll(String petId) {
public void delete(String petId, Long invitedId) {
ops.delete(KEY + ":" + petId, invitedId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ public void save(Long invitedId, Long petId) {
@Override
public List<InvitationDto> findAll(Long petId) {
Map<Long, LocalDateTime> all = managerInvitationRepository.findAll(petId.toString());

all.forEach((k, v) -> {
boolean expired = v.isBefore(LocalDateTime.now());

if (expired) {
log.warn("manager invitation expired. about User : {}", k);
delete(k, petId);
}
});

return all.entrySet().stream()
.filter(entry -> !entry.getValue().isBefore(LocalDateTime.now()))
.map(entry -> InvitationDto.of(petId, entry.getKey(), entry.getValue()))
.toList();
}
Expand All @@ -44,7 +55,14 @@ public boolean expired(Long invitedId, Long petId) {
LocalDateTime ttl = managerInvitationRepository.getTtl(petId.toString(), invitedId);
log.info("manager invitation ttl : {}", ttl);

return ttl.isBefore(LocalDateTime.now());
boolean expired = ttl.isBefore(LocalDateTime.now());

if (expired) {
log.warn("manager invitation expired. about User : {}", invitedId);
delete(invitedId, petId);
return true;
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.co.fitapet.domain.domains.memo.repository;

import java.util.List;

public interface MemoImageQueryDslRepository {
List<String> findMemoImageUrlsByMemoIds(List<Long> memoIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package kr.co.fitapet.domain.domains.memo.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import kr.co.fitapet.domain.domains.memo.domain.QMemo;
import kr.co.fitapet.domain.domains.memo.domain.QMemoImage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@RequiredArgsConstructor
@Slf4j
public class MemoImageQueryDslRepositoryImpl implements MemoImageQueryDslRepository {
private final JPAQueryFactory queryFactory;
private final QMemo memo = QMemo.memo;
private final QMemoImage memoImage = QMemoImage.memoImage;

@Override
public List<String> findMemoImageUrlsByMemoIds(List<Long> memoIds) {
return queryFactory
.select(memoImage.imgUrl)
.from(memoImage)
.where(memoImage.memo.id.in(memoIds))
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import java.util.List;

public interface MemoImageRepository extends ExtendedRepository<MemoImage, Long> {
public interface MemoImageRepository extends ExtendedRepository<MemoImage, Long>, MemoImageQueryDslRepository {
List<MemoImage> findByMemo_Id(Long memoId);
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("delete from MemoImage mi where mi in :memoImages")
Expand Down
Loading
Loading