diff --git a/src/main/java/com/e2i1/linkeepserver/common/constant/NicknameConst.java b/src/main/java/com/e2i1/linkeepserver/common/constant/NicknameConst.java index ad66e53..053d248 100644 --- a/src/main/java/com/e2i1/linkeepserver/common/constant/NicknameConst.java +++ b/src/main/java/com/e2i1/linkeepserver/common/constant/NicknameConst.java @@ -25,18 +25,17 @@ public class NicknameConst { ); public static final List NOUNS = Arrays.asList( - "사과", "바나나", "커피", "책", "차", "고양이", "개", "꽃", "정원", "집", - "섬", "주스", "키스", "나무", "달", "산", "새", "바다", "태양", "교통", - "우산", "마을", "벽", "소", "시계", "노트북", "사진", "카메라", "라디오", "텔레비전", - "핸드폰", "냉장고", "의자", "책상", "문", "창문", "시계", "램프", "거울", "그림", - "침대", "담요", "이불", "베개", "소파", "커튼", "브러시", "병", "컵", "접시", - "식탁", "커트러리", "나이프", "포크", "스푼", "전자레인지", "오븐", "요리", "음악", "노래", - "춤", "영화", "비디오", "게임", "파티", "휴일", "선물", "카드", "편지", "책장", - "화분", "식물", "색깔", "도구", "망치", "못", "조각", "패턴", "디자인", "모양", - "크기", "무게", "공원", "길", "다리", "강", "숲", "계절", "여름", "가을", - "겨울", "봄", "행복", "사랑", "기쁨", "슬픔", "분노", "두려움", "기대", "실망", - "호기심", "평화", "전쟁", "뉴스", "신문", "잡지", "보고서", "이메일", "인터넷", "웹사이트", - "프로그램", "소프트웨어", "하드웨어", "네트워크", "데이터" + "호랑이", "사자", "기린", "코끼리", "하마", "코뿔소", "얼룩말", "늑대", "여우", "곰", + "판다", "북극곰", "사슴", "갈매기", "앵무새", "백조", "펭귄", "돌고래", "상어", "고래", + "뱀", "도마뱀", "악어", "거북", "이구아나", "개구리", "두꺼비", "매", "독수리", "부엉이", + "고양이", "개", "햄스터", "기니피그", "토끼", "비버", "다람쥐", "너구리", "족제비", "몽구스", + "침팬지", "오랑우탄", "고릴라", "원숭이", "마멋", "청설모", "스컹크", "수달", "하이에나", "아르마딜로", + "캥거루", "코알라", "타조", "에뮤", "알파카", "라마", "플라밍고", "악어새", "바다표범", "해마", + "큰바다거북", "칠면조", "오리", "거위", "백로", "황새", "공작", "비둘기", "까치", "앵무새", + "설치류", "미어캣", "코요테", "울버린", "수달", "멧돼지", "호저", "마코호크", "콘도르", "박쥐", + "청개구리", "이리", "바다사자", "미어캣", "와토가이", "필베", "누", "흰머리독수리", "황금독수리", "가젤", + "영양", "바비루사", "노새", "올빼미", "바실리스크", "모래뱀", "보아뱀", "갈라파고스거북", "점박이물범", "포로투칸", + "알바트로스", "오코피", "바다코끼리", "타프", "림프", "마자람" ); public static final int WORD_NUM = NOUNS.size(); diff --git a/src/main/java/com/e2i1/linkeepserver/common/constant/PageConst.java b/src/main/java/com/e2i1/linkeepserver/common/constant/PageConst.java new file mode 100644 index 0000000..503c33b --- /dev/null +++ b/src/main/java/com/e2i1/linkeepserver/common/constant/PageConst.java @@ -0,0 +1,10 @@ +package com.e2i1.linkeepserver.common.constant; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PageConst { + public static final String DEFAULT_PAGE_SIZE = "10"; + +} diff --git a/src/main/java/com/e2i1/linkeepserver/common/error/ErrorCode.java b/src/main/java/com/e2i1/linkeepserver/common/error/ErrorCode.java index aec2043..36bb121 100644 --- a/src/main/java/com/e2i1/linkeepserver/common/error/ErrorCode.java +++ b/src/main/java/com/e2i1/linkeepserver/common/error/ErrorCode.java @@ -24,6 +24,7 @@ public enum ErrorCode{ // 유저 관련 에러 코드 USER_NOT_FOUND(34040,HttpStatus.NOT_FOUND, "사용자를 찾을 수 없음"), + NICKNAME_DUPLICATED(34000,HttpStatus.BAD_REQUEST, "닉네임이 중복됩니다."), // 모음집 관련 에러 코드 COLLECTION_NOT_FOUND(44040,HttpStatus.NOT_FOUND, "모음집을 찾을 수 없음"), @@ -47,6 +48,8 @@ public enum ErrorCode{ RETRY_EXCEEDED(85000, HttpStatus.INTERNAL_SERVER_ERROR, "랜덤 닉네임 생성 재시도 횟수를 초과했습니다."), + //좋아요 관련 에러 코드 + LIKE_NOT_FOUND(94040,HttpStatus.NOT_FOUND,"사용자가 모음집을 좋아요한 기록이 없습니다.") ; private final int errorCode; diff --git a/src/main/java/com/e2i1/linkeepserver/config/WebConfig.java b/src/main/java/com/e2i1/linkeepserver/config/WebConfig.java index dfc18f8..9d86690 100644 --- a/src/main/java/com/e2i1/linkeepserver/config/WebConfig.java +++ b/src/main/java/com/e2i1/linkeepserver/config/WebConfig.java @@ -20,7 +20,8 @@ public class WebConfig implements WebMvcConfigurer { "/", "favicon.ico", "/error", - "/api/users/login" + "/api/users/login", + "/api/users/register" ); private final List SWAGGER = List.of( diff --git a/src/main/java/com/e2i1/linkeepserver/domain/collections/business/CollectionsBusiness.java b/src/main/java/com/e2i1/linkeepserver/domain/collections/business/CollectionsBusiness.java index 6542ded..3eafc40 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/collections/business/CollectionsBusiness.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/collections/business/CollectionsBusiness.java @@ -83,11 +83,20 @@ public List getTitle(UsersEntity user){ } @Transactional - public Long updateNumOfLikes(Long collectionId,UsersEntity user){ + public Long updateNumOfLikes(Long collectionId,UsersEntity user, boolean isFlag){ CollectionsEntity collection = collectionsService.findByIdWithThrow(collectionId); - LikeOthersEntity likeOther = likeOthersConverter.toLikeOthersEntity(collection,user); - likeOthersService.updateLike(likeOther); - collection.updateLikes(); + + if(isFlag){ + LikeOthersEntity likeOther = likeOthersConverter.toLikeOthersEntity(collection,user); + likeOthersService.updateLike(likeOther); + collection.addLikes(); + } + else{ + LikeOthersEntity likeOther = likeOthersService.findByCollectionAndUser(collection,user); + likeOthersService.deleteLike(likeOther); + collection.deleteLikes(); + + } return collection.getNumOfLikes(); } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/collections/controller/CollectionsController.java b/src/main/java/com/e2i1/linkeepserver/domain/collections/controller/CollectionsController.java index 4d01269..5133bb4 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/collections/controller/CollectionsController.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/collections/controller/CollectionsController.java @@ -48,13 +48,12 @@ public ResponseEntity> getCollectionTitle(@UserSessi public ResponseEntity getCollection(@PathVariable Long collectionId, @UserSession UsersEntity user){ return ResponseEntity.ok(collectionsBusiness.getCollection(collectionId, user)); - } @PostMapping("/collections/like") - public ResponseEntity> countLike(@RequestBody CollectionLikeReqDTO likecollection,@UserSession UsersEntity user){ + public ResponseEntity> countLike(@RequestBody CollectionLikeReqDTO likeCollection,@UserSession UsersEntity user){ HashMap result = new HashMap<>(); - Long numOfLikes = collectionsBusiness.updateNumOfLikes(likecollection.getCollectionId(),user); + Long numOfLikes = collectionsBusiness.updateNumOfLikes(likeCollection.getCollectionId(),user, likeCollection.isFlag()); result.put("numOfLikes", numOfLikes); return ResponseEntity.ok(result); } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/collections/dto/CollectionLikeReqDTO.java b/src/main/java/com/e2i1/linkeepserver/domain/collections/dto/CollectionLikeReqDTO.java index 53347cb..90a7598 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/collections/dto/CollectionLikeReqDTO.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/collections/dto/CollectionLikeReqDTO.java @@ -11,5 +11,5 @@ @Builder public class CollectionLikeReqDTO { private long collectionId; - + private boolean flag; } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/collections/entity/CollectionsEntity.java b/src/main/java/com/e2i1/linkeepserver/domain/collections/entity/CollectionsEntity.java index e319bee..9a46f6c 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/collections/entity/CollectionsEntity.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/collections/entity/CollectionsEntity.java @@ -61,8 +61,11 @@ public void addTag(TagsEntity tag) { tag.setCollection(this); } - public void updateLikes(){ + public void addLikes(){ this.numOfLikes += 1L; } + public void deleteLikes() { + this.numOfLikes -=1L; + } } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/likeothers/service/LikeOthersService.java b/src/main/java/com/e2i1/linkeepserver/domain/likeothers/service/LikeOthersService.java index cfb4809..2de4e00 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/likeothers/service/LikeOthersService.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/likeothers/service/LikeOthersService.java @@ -28,4 +28,12 @@ public List findCollectionByUser(UsersEntity user) { return likeOthersRepository.findCollectionByUser(user).orElseThrow(() -> new ApiException( ErrorCode.COLLABORATOR_NOT_FOUND)); } + + public void deleteLike(LikeOthersEntity likeOther) { + likeOthersRepository.delete(likeOther); + } + + public LikeOthersEntity findByCollectionAndUser(CollectionsEntity collection, UsersEntity user) { + return likeOthersRepository.findByCollectionAndUser(collection,user).orElseThrow(() -> new ApiException(ErrorCode.LIKE_NOT_FOUND)); + } } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/links/business/LinksBusiness.java b/src/main/java/com/e2i1/linkeepserver/domain/links/business/LinksBusiness.java index db53d4e..586f6e6 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/links/business/LinksBusiness.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/links/business/LinksBusiness.java @@ -17,6 +17,8 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.transaction.annotation.Transactional; @Business @@ -87,11 +89,19 @@ public List searchLinks(String keyword) { * 유저가 저장한 모든 link list 불러오기 * 최신 순으로 정렬해서 */ - public List findByUserId(Long userId) { - List linkList = linksService.findByUserId(userId); + public List findByUserId(Long userId, Long lastId, int size) { + Pageable pageable = PageRequest.of(0, size); + List linkList = linksService.findByUserId(userId, lastId, pageable); return linkList.stream() .map(linksConverter::toLinkHomeResponse) .collect(Collectors.toList()); } + + /** + * 다음에 조회될 링크가 있는지 여부 + */ + public Boolean hasNext(Long id) { + return linksService.hasNext(id); + } } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/links/repository/LinksRepository.java b/src/main/java/com/e2i1/linkeepserver/domain/links/repository/LinksRepository.java index 6a98a9a..3c5a227 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/links/repository/LinksRepository.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/links/repository/LinksRepository.java @@ -5,6 +5,7 @@ import io.lettuce.core.dynamic.annotation.Param; import jakarta.persistence.LockModeType; import java.util.List; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -24,5 +25,7 @@ public interface LinksRepository extends JpaRepository { List findLinksEntitiesByCollection(CollectionsEntity collectionsEntity); - List findByUserIdOrderByUpdateAtDesc(Long userId); + List findByUserIdAndIdLessThanOrderByIdDesc(Long userId, Long lastId, Pageable pageable); + + Boolean existsByIdLessThan(Long id); } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/links/service/LinksService.java b/src/main/java/com/e2i1/linkeepserver/domain/links/service/LinksService.java index f241a85..277f5f1 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/links/service/LinksService.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/links/service/LinksService.java @@ -1,13 +1,14 @@ package com.e2i1.linkeepserver.domain.links.service; -import com.e2i1.linkeepserver.domain.collections.entity.CollectionsEntity; import com.e2i1.linkeepserver.common.error.ErrorCode; import com.e2i1.linkeepserver.common.exception.ApiException; +import com.e2i1.linkeepserver.domain.collections.entity.CollectionsEntity; import com.e2i1.linkeepserver.domain.links.entity.LinksEntity; import com.e2i1.linkeepserver.domain.links.repository.LinksRepository; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,7 +47,11 @@ public List searchLinks(String keyword) { return linksRepository.findByTitleOrDescriptionContainingKeyword(keyword); } - public List findByUserId(Long userId) { - return linksRepository.findByUserIdOrderByUpdateAtDesc(userId); + public List findByUserId(Long userId, Long lastId, Pageable pageable) { + return linksRepository.findByUserIdAndIdLessThanOrderByIdDesc(userId, lastId, pageable); + } + + public Boolean hasNext(Long id) { + return linksRepository.existsByIdLessThan(id); } } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/token/entity/BlackList.java b/src/main/java/com/e2i1/linkeepserver/domain/token/entity/BlackList.java new file mode 100644 index 0000000..1ea79c3 --- /dev/null +++ b/src/main/java/com/e2i1/linkeepserver/domain/token/entity/BlackList.java @@ -0,0 +1,25 @@ +package com.e2i1.linkeepserver.domain.token.entity; + +import static lombok.AccessLevel.PROTECTED; + +import com.e2i1.linkeepserver.common.entity.BaseEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@EqualsAndHashCode(callSuper = true) +@SuperBuilder +@Getter +@NoArgsConstructor(access = PROTECTED) +@AllArgsConstructor +@Entity +@Table(name = "token_black_list") +public class BlackList extends BaseEntity { + + private String invalidToken; + +} diff --git a/src/main/java/com/e2i1/linkeepserver/domain/token/repository/BlackListRepository.java b/src/main/java/com/e2i1/linkeepserver/domain/token/repository/BlackListRepository.java new file mode 100644 index 0000000..909b541 --- /dev/null +++ b/src/main/java/com/e2i1/linkeepserver/domain/token/repository/BlackListRepository.java @@ -0,0 +1,10 @@ +package com.e2i1.linkeepserver.domain.token.repository; + +import com.e2i1.linkeepserver.domain.token.entity.BlackList; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface BlackListRepository extends JpaRepository { + + BlackList findByInvalidToken(String token); +} diff --git a/src/main/java/com/e2i1/linkeepserver/domain/token/service/TokenService.java b/src/main/java/com/e2i1/linkeepserver/domain/token/service/TokenService.java index 840b719..b85ef63 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/token/service/TokenService.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/token/service/TokenService.java @@ -3,19 +3,21 @@ import com.e2i1.linkeepserver.common.error.ErrorCode; import com.e2i1.linkeepserver.common.exception.ApiException; import com.e2i1.linkeepserver.domain.token.dto.TokenDTO; +import com.e2i1.linkeepserver.domain.token.entity.BlackList; import com.e2i1.linkeepserver.domain.token.ifs.TokenHelperIfs; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - +import com.e2i1.linkeepserver.domain.token.repository.BlackListRepository; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class TokenService { private final TokenHelperIfs tokenHelperIfs; + private final BlackListRepository blackListRepository; public TokenDTO issueAccessToken(Long userId) { HashMap data = new HashMap<>(); @@ -39,9 +41,19 @@ public Long validationToken(String token) { // userId 없을 수 있음 -> null check Objects.requireNonNull(userId, () -> { - throw new ApiException(ErrorCode.NULL_POINT); + throw new ApiException(ErrorCode.INVALID_TOKEN); }); + // 해당 token이 blacklist에 저장되어 있는지 확인 -> 저장되어 있으면 예외 던짐 + BlackList invalidToken = blackListRepository.findByInvalidToken(token); + if (invalidToken != null) { + throw new ApiException(ErrorCode.INVALID_TOKEN, "로그아웃 된 token입니다."); + } + return Long.parseLong(userId.toString()); } + + public void saveBlackList(BlackList blackList) { + blackListRepository.save(blackList); + } } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/business/UsersBusiness.java b/src/main/java/com/e2i1/linkeepserver/domain/users/business/UsersBusiness.java index 77a1b7d..123be86 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/users/business/UsersBusiness.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/business/UsersBusiness.java @@ -12,12 +12,16 @@ import com.e2i1.linkeepserver.domain.links.business.LinksBusiness; import com.e2i1.linkeepserver.domain.token.business.TokenBusiness; import com.e2i1.linkeepserver.domain.token.dto.TokenResDTO; +import com.e2i1.linkeepserver.domain.token.entity.BlackList; +import com.e2i1.linkeepserver.domain.token.service.TokenService; import com.e2i1.linkeepserver.domain.users.converter.UsersConverter; import com.e2i1.linkeepserver.domain.users.dto.EditProfileReqDTO; import com.e2i1.linkeepserver.domain.users.dto.LinkHomeResDTO; import com.e2i1.linkeepserver.domain.users.dto.LoginReqDTO; +import com.e2i1.linkeepserver.domain.users.dto.LoginResDTO; import com.e2i1.linkeepserver.domain.users.dto.NicknameResDTO; import com.e2i1.linkeepserver.domain.users.dto.ProfileResDTO; +import com.e2i1.linkeepserver.domain.users.dto.SignupReqDTO; import com.e2i1.linkeepserver.domain.users.dto.UserHomeResDTO; import com.e2i1.linkeepserver.domain.users.entity.UsersEntity; import com.e2i1.linkeepserver.domain.users.service.UsersService; @@ -31,6 +35,7 @@ @Business @RequiredArgsConstructor public class UsersBusiness { + private final UsersService usersService; private final UsersConverter usersConverter; @@ -38,59 +43,68 @@ public class UsersBusiness { private final LinksBusiness linksBusiness; private final S3ImageService s3ImageService; + private final TokenService tokenService; @Transactional - public TokenResDTO login(LoginReqDTO loginReqDTO) { + public LoginResDTO login(LoginReqDTO loginReqDTO) { UsersEntity user = usersService.getUser(loginReqDTO.getEmail()); - // 로그인 시, 해당 유저 없으면 자동 회원가입 진행 + // 로그인 시, 해당 유저 없으면 랜덤 닉네임만 만들어서 반환 if (user == null) { - String nickname = null; - int attempts = 0; - - // RETRY_NUM 만큼 닉네임 생성 시도 - while (attempts < RETRY_NUM) { - Random random = new Random(); - int randomNum1 = random.nextInt(WORD_NUM); - int randomNum2 = random.nextInt(WORD_NUM); - int randomNum3 = random.nextInt(WORD_NUM) + 1; + String nickname = createRandomNickname(); - // 랜덤 닉네임 만들기 - nickname = ADJECTIVES.get(randomNum1) + " " + NOUNS.get(randomNum2) + randomNum3; - - // 닉네임 존재하는지 확인 - Boolean isDuplicated = usersService.isDuplicatedNickname(nickname); + // 신규 유저면 randomNickname 만들어서 반환하기 + return LoginResDTO.builder() + .existedUser(false) + .randomNickname(nickname) + .build(); + } - // 존재하지 않으면 해당 닉네임으로 저장 - if (!isDuplicated) { - break; - } + // 기존 유저면 token 발행해서 반환하기 + return LoginResDTO.builder() + .tokenDTO(tokenBusiness.issueToken(user)) + .existedUser(true).build(); + } - // 해당 닉네임 존재 시, 다시 시도 - attempts++; - } - if (attempts == 10) { - throw new ApiException(ErrorCode.RETRY_EXCEEDED); - } + /** + * 신규 회원 가입 로직 + */ + @Transactional + public TokenResDTO signup(SignupReqDTO signupInfo, MultipartFile img) { + // 닉네임 unique한지 검증 + if (!validateDuplicatedNickname(signupInfo.getNickname())) { + throw new ApiException(ErrorCode.NICKNAME_DUPLICATED); + } - loginReqDTO.setNickname(nickname); - UsersEntity newUser = usersConverter.toEntity(loginReqDTO); - user = usersService.register(newUser); + // 이미지 존재하면 S3에 저장하고 URL 반환받기 + String imgUrl = null; + if (img != null && !img.isEmpty()) { + imgUrl = s3ImageService.upload(img); } - // 토큰 발행 - return tokenBusiness.issueToken(user); - } + // signupInfo(nickname, email 들어있음)와 이미지URL로 UsersEntity 만들고 저장하기 + UsersEntity newUser = usersConverter.toEntity(signupInfo, imgUrl); + newUser = usersService.register(newUser); - public UserHomeResDTO getUserHome(UsersEntity user) { - // user의 모든 link를 최신순으로 가져오기 - List linkHomeList = linksBusiness.findByUserId(user.getId()); + // 저장된 newUser로 token 생성하기 + return tokenBusiness.issueToken(newUser); + } + public UserHomeResDTO getUserHome(Long lastId, Integer size, UsersEntity user) { + if (lastId == null) { + lastId = Long.MAX_VALUE; // lastId가 null인 경우 가능한 가장 큰 ID부터 시작 + } + // lastId부터 size만큼 링크 가져오기 + List linkHomeList = linksBusiness.findByUserId(user.getId(), lastId, size); + + // 가져온 링크들의 마지막 id 값을 가지고 다음에 조회할 링크 있는지 확인 + Boolean hasNext = linksBusiness.hasNext(linkHomeList.get(linkHomeList.size()-1).getId()); return UserHomeResDTO.builder() .nickname(user.getNickname()) .imgUrl(user.getImgUrl()) .linkList(linkHomeList) + .hasNext(hasNext) .build(); } @@ -111,12 +125,14 @@ public ProfileResDTO getProfile(UsersEntity user) { } @Transactional - public void editProfile(MultipartFile imgFile, EditProfileReqDTO editProfile, UsersEntity user) { + public void editProfile(MultipartFile imgFile, EditProfileReqDTO editProfile, + UsersEntity user) { // 기본 값으로 기존 유저의 ImgUrl 사용 String imgUrl = user.getImgUrl(); // 기존 이미지가 존재하고 이를 삭제하는 경우 - if (Boolean.TRUE.equals(editProfile.getIsDeletedImg()) && imgUrl != null) { // editProfile.getIsDeletedImg가 null인 경우 false로 인식하기 위해 Boolean.TRUE.equals 사용 + if (Boolean.TRUE.equals(editProfile.getIsDeletedImg()) && imgUrl + != null) { // editProfile.getIsDeletedImg가 null인 경우 false로 인식하기 위해 Boolean.TRUE.equals 사용 s3ImageService.deleteImageFromS3(user.getImgUrl()); imgUrl = null; } @@ -131,13 +147,64 @@ public void editProfile(MultipartFile imgFile, EditProfileReqDTO editProfile, Us } + /** + * 로그아웃 로직 + * 현재 유저에게 발급한 token을 blacklist에 저장해서 다시 사용하지 못하도록 + */ + public void logout(String token, UsersEntity user) { + Long userID = tokenBusiness.validationAccessToken(token); + if (!user.getId().equals(userID)) { + throw new ApiException(ErrorCode.ACCESS_DENIED, "로그인한 유저의 ID와 token의 유저 ID가 다릅니다."); + } + + // 해당 token을 blackList에 저장 + tokenService.saveBlackList(BlackList.builder().invalidToken(token).build()); + } + /** - * nickname이 유니크하면 true - * 유니크하지 않으면 false + * nickname이 유니크하면 true 유니크하지 않으면 false */ public Boolean validateDuplicatedNickname(String nickname) { Boolean isDuplicated = usersService.isDuplicatedNickname(nickname); return !isDuplicated; } + + /** + * 랜덤 닉네임 만드는 로직 + */ + private String createRandomNickname() { + String nickname = null; + int attempts = 0; + + // RETRY_NUM 만큼 닉네임 생성 시도 + while (attempts < RETRY_NUM) { + Random random = new Random(); + int randomNum1 = random.nextInt(WORD_NUM); + int randomNum2 = random.nextInt(WORD_NUM); + int randomNum3 = random.nextInt(WORD_NUM) + 1; + + // 랜덤 닉네임 만들기 + nickname = ADJECTIVES.get(randomNum1) + " " + NOUNS.get(randomNum2) + randomNum3; + + // 닉네임 존재하는지 확인 + Boolean isDuplicated = usersService.isDuplicatedNickname(nickname); + + // 존재하지 않으면 해당 닉네임으로 저장 + if (!isDuplicated) { + break; + } + + // 해당 닉네임 존재 시, 다시 시도 + attempts++; + } + // 10번 재시도를 넘었다면 에러 발생 + if (attempts == 10) { + throw new ApiException(ErrorCode.RETRY_EXCEEDED); + } + + return nickname; + } + + } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/controller/UsersController.java b/src/main/java/com/e2i1/linkeepserver/domain/users/controller/UsersController.java index 1845ca9..84cd2e2 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/users/controller/UsersController.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/controller/UsersController.java @@ -1,21 +1,32 @@ package com.e2i1.linkeepserver.domain.users.controller; +import static com.e2i1.linkeepserver.common.constant.PageConst.DEFAULT_PAGE_SIZE; + import com.e2i1.linkeepserver.common.annotation.UserSession; import com.e2i1.linkeepserver.domain.token.dto.TokenResDTO; import com.e2i1.linkeepserver.domain.users.business.UsersBusiness; import com.e2i1.linkeepserver.domain.users.dto.EditProfileReqDTO; import com.e2i1.linkeepserver.domain.users.dto.LoginReqDTO; +import com.e2i1.linkeepserver.domain.users.dto.LoginResDTO; import com.e2i1.linkeepserver.domain.users.dto.NicknameResDTO; import com.e2i1.linkeepserver.domain.users.dto.ProfileResDTO; +import com.e2i1.linkeepserver.domain.users.dto.SignupReqDTO; import com.e2i1.linkeepserver.domain.users.dto.UserHomeResDTO; import com.e2i1.linkeepserver.domain.users.entity.UsersEntity; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @Slf4j @@ -27,8 +38,9 @@ public class UsersController { private final UsersBusiness usersBusiness; @GetMapping("/home") - public ResponseEntity getUserHome(@UserSession UsersEntity user) { - UserHomeResDTO home = usersBusiness.getUserHome(user); + public ResponseEntity getUserHome(@RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam(value = "size", defaultValue = DEFAULT_PAGE_SIZE) Integer size, @UserSession UsersEntity user) { + UserHomeResDTO home = usersBusiness.getUserHome(lastId, size, user); return ResponseEntity.ok(home); } @@ -65,11 +77,29 @@ public ResponseEntity isDuplicatedNickName(@RequestParam String nicknam } @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginReqDTO loginReqDTO) { - TokenResDTO response = usersBusiness.login(loginReqDTO); + public ResponseEntity login(@RequestBody LoginReqDTO loginReqDTO) { + LoginResDTO response = usersBusiness.login(loginReqDTO); + + return ResponseEntity.ok(response); + } + + @PostMapping("/register") + public ResponseEntity signup( + @RequestPart(value = "image", required = false) MultipartFile imgFile, + @RequestPart SignupReqDTO signupInfo + ) { + TokenResDTO response = usersBusiness.signup(signupInfo, imgFile); return ResponseEntity.ok(response); } + @PostMapping("/logout") + public ResponseEntity logout(@RequestHeader("authorization-token") String token, + @UserSession UsersEntity user) { + usersBusiness.logout(token, user); + + return ResponseEntity.ok("로그아웃 되었습니다."); + } + } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/converter/UsersConverter.java b/src/main/java/com/e2i1/linkeepserver/domain/users/converter/UsersConverter.java index 376c8a6..86ca13b 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/users/converter/UsersConverter.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/converter/UsersConverter.java @@ -1,8 +1,8 @@ package com.e2i1.linkeepserver.domain.users.converter; import com.e2i1.linkeepserver.common.annotation.Converter; -import com.e2i1.linkeepserver.domain.users.dto.LoginReqDTO; import com.e2i1.linkeepserver.domain.users.dto.NicknameResDTO; +import com.e2i1.linkeepserver.domain.users.dto.SignupReqDTO; import com.e2i1.linkeepserver.domain.users.entity.UserStatus; import com.e2i1.linkeepserver.domain.users.entity.UsersEntity; import lombok.RequiredArgsConstructor; @@ -10,15 +10,13 @@ @Converter @RequiredArgsConstructor public class UsersConverter { - public UsersEntity toEntity(LoginReqDTO loginReqDTO) { + public UsersEntity toEntity(SignupReqDTO signupInfo, String imgUrl) { return UsersEntity.builder() - .nickname(loginReqDTO.getNickname()) - .email(loginReqDTO.getEmail()) - .imgUrl(loginReqDTO.getImgUrl()) - .thumbnailUrl(loginReqDTO.getImgUrl()) - .status(UserStatus.REGISTERED) - .build(); - + .email(signupInfo.getEmail()) + .nickname(signupInfo.getNickname()) + .imgUrl(imgUrl) + .status(UserStatus.REGISTERED) + .build(); } public NicknameResDTO toNicknameResponse(UsersEntity user) { diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginReqDTO.java b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginReqDTO.java index 749bf2e..cc0473c 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginReqDTO.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginReqDTO.java @@ -10,8 +10,5 @@ @NoArgsConstructor @AllArgsConstructor public class LoginReqDTO { - private String nickname; private String email; - private String imgUrl; - } diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginResDTO.java b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginResDTO.java new file mode 100644 index 0000000..2a28fe7 --- /dev/null +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/LoginResDTO.java @@ -0,0 +1,20 @@ +package com.e2i1.linkeepserver.domain.users.dto; + +import com.e2i1.linkeepserver.domain.token.dto.TokenResDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginResDTO { + private TokenResDTO tokenDTO; + + private String randomNickname; + + private Boolean existedUser; + +} diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/dto/SignupReqDTO.java b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/SignupReqDTO.java new file mode 100644 index 0000000..c09c21d --- /dev/null +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/SignupReqDTO.java @@ -0,0 +1,15 @@ +package com.e2i1.linkeepserver.domain.users.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SignupReqDTO { + private String email; + private String nickname; +} diff --git a/src/main/java/com/e2i1/linkeepserver/domain/users/dto/UserHomeResDTO.java b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/UserHomeResDTO.java index 7dc7231..eb5a6e9 100644 --- a/src/main/java/com/e2i1/linkeepserver/domain/users/dto/UserHomeResDTO.java +++ b/src/main/java/com/e2i1/linkeepserver/domain/users/dto/UserHomeResDTO.java @@ -15,4 +15,5 @@ public class UserHomeResDTO { private String nickname; private String imgUrl; private List linkList; + private Boolean hasNext; // 커서 기반 페이징할 때, 끝인지 아닌지 알려주는 변수 }