diff --git a/.github/workflows/backend-dev-cd.yml b/.github/workflows/backend-dev-cd.yml index 4c807bee2..1e51c0343 100644 --- a/.github/workflows/backend-dev-cd.yml +++ b/.github/workflows/backend-dev-cd.yml @@ -7,7 +7,7 @@ on: jobs: deploy: - runs-on: self-hosted + runs-on: backend-dev-cd steps: - name: 배포 스크립트 실행 diff --git a/.gitignore b/.gitignore index be58d40a8..e7a649a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -192,3 +192,4 @@ fabric.properties !/gradle/wrapper/gradle-wrapper.jar # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,kotlin +/backend/src/main/generated/ diff --git a/backend/build.gradle b/backend/build.gradle index ba8e44d63..86a5c24bb 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -21,25 +21,45 @@ repositories { } dependencies { + // spring implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + + // configuration processor + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" + + // jwt implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // h2 runtimeOnly 'com.h2database:h2' + + // mysql runtimeOnly 'com.mysql:mysql-connector-j' + + // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.0' + // flyway implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' + // lombok compileOnly 'org.projectlombok:lombok:1.18.28' annotationProcessor 'org.projectlombok:lombok:1.18.28' + + // querydsl + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' + annotationProcessor 'jakarta.annotation:jakarta.annotation-api' + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' } processResources.dependsOn('copySecret') @@ -52,3 +72,23 @@ tasks.register('copySecret', Copy) { tasks.named('test') { useJUnitPlatform() } + +// configuration processor +tasks.named('compileJava') { + inputs.files(tasks.named('processResources')) +} + +// Querydsl +def querydslDir = "$buildDir/generated/querydsl" + +sourceSets { + main.java.srcDirs += [querydslDir] +} + +tasks.withType(JavaCompile).configureEach { + options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir) +} + +clean.doLast { + file(querydslDir).deleteDir() +} diff --git a/backend/src/main/java/dev/tripdraw/BackendApplication.java b/backend/src/main/java/dev/tripdraw/BackendApplication.java index 6be14c569..85f79ef5e 100644 --- a/backend/src/main/java/dev/tripdraw/BackendApplication.java +++ b/backend/src/main/java/dev/tripdraw/BackendApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +@ConfigurationPropertiesScan @SpringBootApplication public class BackendApplication { diff --git a/backend/src/main/java/dev/tripdraw/application/AuthService.java b/backend/src/main/java/dev/tripdraw/application/AuthService.java deleted file mode 100644 index 07106d356..000000000 --- a/backend/src/main/java/dev/tripdraw/application/AuthService.java +++ /dev/null @@ -1,66 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.exception.member.MemberExceptionType.DUPLICATE_NICKNAME; -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; - -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.application.oauth.OauthClient; -import dev.tripdraw.application.oauth.OauthClientProvider; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.dto.auth.OauthInfo; -import dev.tripdraw.dto.auth.OauthRequest; -import dev.tripdraw.dto.auth.OauthResponse; -import dev.tripdraw.dto.auth.RegisterRequest; -import dev.tripdraw.exception.member.MemberException; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Transactional -@Service -public class AuthService { - - private static final String EMPTY_TOKEN = ""; - - private final MemberRepository memberRepository; - private final OauthClientProvider oauthClientProvider; - private final AuthTokenManager authTokenManager; - - public OauthResponse login(OauthRequest oauthRequest) { - OauthClient oauthClient = oauthClientProvider.provide(oauthRequest.oauthType()); - OauthInfo oauthInfo = oauthClient.requestOauthInfo(oauthRequest.oauthToken()); - - String accessToken = memberRepository.findByOauthIdAndOauthType(oauthInfo.oauthId(), oauthInfo.oauthType()) - .map(member -> authTokenManager.generate(member.id())) - .orElse(EMPTY_TOKEN); - - if (accessToken.isEmpty()) { - memberRepository.save(Member.of(oauthInfo.oauthId(), oauthInfo.oauthType())); - } - - return new OauthResponse(accessToken); - } - - public OauthResponse register(RegisterRequest registerRequest) { - OauthClient oauthClient = oauthClientProvider.provide(registerRequest.oauthType()); - OauthInfo oauthInfo = oauthClient.requestOauthInfo(registerRequest.oauthToken()); - - Member member = memberRepository.findByOauthIdAndOauthType(oauthInfo.oauthId(), oauthInfo.oauthType()) - .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - - String nickname = registerRequest.nickname(); - validateDuplicateNickname(nickname); - member.changeNickname(nickname); - - String accessToken = authTokenManager.generate(member.id()); - return new OauthResponse(accessToken); - } - - private void validateDuplicateNickname(String nickname) { - if (memberRepository.existsByNickname(nickname)) { - throw new MemberException(DUPLICATE_NICKNAME); - } - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/MemberService.java b/backend/src/main/java/dev/tripdraw/application/MemberService.java deleted file mode 100644 index 86b550f41..000000000 --- a/backend/src/main/java/dev/tripdraw/application/MemberService.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; - -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.member.MemberSearchResponse; -import dev.tripdraw.exception.member.MemberException; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Transactional -@Service -public class MemberService { - - private final MemberRepository memberRepository; - private final TripRepository tripRepository; - private final PostRepository postRepository; - private final AuthTokenManager authTokenManager; - - @Transactional(readOnly = true) - public boolean existsById(Long memberId) { - return memberRepository.existsById(memberId); - } - - @Transactional(readOnly = true) - public MemberSearchResponse findByCode(String code) { - Long memberId = authTokenManager.extractMemberId(code); - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - return MemberSearchResponse.from(member); - } - - public void deleteByCode(String code) { - Long memberId = authTokenManager.extractMemberId(code); - postRepository.deleteByMemberId(memberId); - tripRepository.deleteByMemberId(memberId); - memberRepository.deleteById(memberId); - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/PostService.java b/backend/src/main/java/dev/tripdraw/application/PostService.java deleted file mode 100644 index 112e8dddf..000000000 --- a/backend/src/main/java/dev/tripdraw/application/PostService.java +++ /dev/null @@ -1,158 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.domain.file.FileType.POST_IMAGE; -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; -import static dev.tripdraw.exception.post.PostExceptionType.POST_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_NOT_FOUND; - -import dev.tripdraw.application.file.FileUploader; -import dev.tripdraw.domain.file.FileType; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.post.PostCreateEvent; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.dto.post.PostAndPointCreateRequest; -import dev.tripdraw.dto.post.PostCreateResponse; -import dev.tripdraw.dto.post.PostRequest; -import dev.tripdraw.dto.post.PostResponse; -import dev.tripdraw.dto.post.PostUpdateRequest; -import dev.tripdraw.dto.post.PostsResponse; -import dev.tripdraw.exception.member.MemberException; -import dev.tripdraw.exception.post.PostException; -import dev.tripdraw.exception.trip.TripException; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@RequiredArgsConstructor -@Transactional -@Service -public class PostService { - - private final PostRepository postRepository; - private final TripRepository tripRepository; - private final MemberRepository memberRepository; - private final FileUploader fileUploader; - private final ApplicationEventPublisher applicationEventPublisher; - - public PostCreateResponse addAtCurrentPoint( - LoginUser loginUser, - PostAndPointCreateRequest postAndPointCreateRequest, - MultipartFile file - ) { - Member member = findMemberById(loginUser.memberId()); - Trip trip = findValidatedTripById(postAndPointCreateRequest.tripId(), member); - - Point point = postAndPointCreateRequest.toPoint(); - trip.add(point); - tripRepository.flush(); - - Post post = postAndPointCreateRequest.toPost(member, point); - Post savedPost = postRepository.save(registerFileToPost(file, post)); - - applicationEventPublisher.publishEvent(new PostCreateEvent(post.id(), trip.id())); - - return PostCreateResponse.from(savedPost); - } - - public PostCreateResponse addAtExistingLocation( - LoginUser loginUser, - PostRequest postRequest, - MultipartFile file - ) { - Member member = findMemberById(loginUser.memberId()); - Trip trip = findValidatedTripById(postRequest.tripId(), member); - - Point point = trip.findPointById(postRequest.pointId()); - - Post post = postRequest.toPost(member, point); - Post savedPost = postRepository.save(registerFileToPost(file, post)); - - applicationEventPublisher.publishEvent(new PostCreateEvent(post.id(), trip.id())); - - return PostCreateResponse.from(savedPost); - } - - public PostResponse read(LoginUser loginUser, Long postId) { - Post post = findPostById(postId); - Member member = findMemberById(loginUser.memberId()); - post.validateAuthorization(member); - return PostResponse.from(post); - } - - public PostsResponse readAllByTripId(LoginUser loginUser, Long tripId) { - Member member = findMemberById(loginUser.memberId()); - findValidatedTripById(tripId, member); - - List posts = postRepository.findAllByTripId(tripId).stream() - .sorted(Comparator.comparing(Post::pointRecordedAt).reversed()) - .collect(Collectors.toList()); - return PostsResponse.from(posts); - } - - public void update(LoginUser loginUser, Long postId, PostUpdateRequest postUpdateRequest, MultipartFile file) { - Post post = findPostById(postId); - Member member = findMemberById(loginUser.memberId()); - post.validateAuthorization(member); - - post.changeTitle(postUpdateRequest.title()); - post.changeWriting(postUpdateRequest.writing()); - updateFileOfPost(file, post); - } - - public void delete(LoginUser loginUser, Long postId) { - Post post = findPostById(postId); - Member member = findMemberById(loginUser.memberId()); - post.validateAuthorization(member); - - postRepository.deleteById(postId); - } - - private void updateFileOfPost(MultipartFile file, Post post) { - if (file == null) { - return; - } - String imageUrl = fileUploader.upload(file, POST_IMAGE); - post.changePostImageUrl(imageUrl); - } - - private Trip findValidatedTripById(Long tripId, Member member) { - - Trip trip = tripRepository.findById(tripId) - .orElseThrow(() -> new TripException(TRIP_NOT_FOUND)); - trip.validateAuthorization(member); - return trip; - } - - private Member findMemberById(Long memberId) { - return memberRepository.findById(memberId) - .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - } - - private Post findPostById(Long postId) { - return postRepository.findById(postId) - .orElseThrow(() -> new PostException(POST_NOT_FOUND)); - } - - private Post registerFileToPost(MultipartFile file, Post post) { - if (file == null) { - return post; - } - FileType type = FileType.from(file.getContentType()); - String fileUrl = fileUploader.upload(file, type); - - post.changePostImageUrl(fileUrl); - return post; - } -} - diff --git a/backend/src/main/java/dev/tripdraw/application/TripService.java b/backend/src/main/java/dev/tripdraw/application/TripService.java deleted file mode 100644 index eb16dba38..000000000 --- a/backend/src/main/java/dev/tripdraw/application/TripService.java +++ /dev/null @@ -1,118 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_NOT_FOUND; - -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.domain.trip.TripUpdateEvent; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.dto.trip.PointCreateRequest; -import dev.tripdraw.dto.trip.PointCreateResponse; -import dev.tripdraw.dto.trip.PointResponse; -import dev.tripdraw.dto.trip.TripCreateResponse; -import dev.tripdraw.dto.trip.TripResponse; -import dev.tripdraw.dto.trip.TripUpdateRequest; -import dev.tripdraw.dto.trip.TripsSearchResponse; -import dev.tripdraw.exception.member.MemberException; -import dev.tripdraw.exception.trip.TripException; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Transactional -@Service -public class TripService { - - private final TripRepository tripRepository; - private final MemberRepository memberRepository; - private final ApplicationEventPublisher applicationEventPublisher; - - public TripCreateResponse create(LoginUser loginUser) { - Member member = getById(loginUser.memberId()); - Trip trip = Trip.from(member); - Trip savedTrip = tripRepository.save(trip); - return TripCreateResponse.from(savedTrip); - } - - public PointCreateResponse addPoint(LoginUser loginUser, PointCreateRequest pointCreateRequest) { - Member member = getById(loginUser.memberId()); - Trip trip = getByTripId(pointCreateRequest.tripId()); - - Point point = pointCreateRequest.toPoint(); - trip.validateAuthorization(member); - trip.add(point); - - tripRepository.flush(); - return PointCreateResponse.from(point); - } - - public void deletePoint(LoginUser loginUser, Long pointId, Long tripId) { - Member member = getById(loginUser.memberId()); - - Trip trip = getByTripId(tripId); - trip.validateAuthorization(member); - - trip.deletePointById(pointId); - } - - private Member getById(Long memberId) { - return memberRepository.findById(memberId) - .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); - } - - private Trip getByTripId(Long tripId) { - return tripRepository.findById(tripId) - .orElseThrow(() -> new TripException(TRIP_NOT_FOUND)); - } - - @Transactional(readOnly = true) - public TripResponse readTripById(LoginUser loginUser, Long id) { - Member member = getById(loginUser.memberId()); - Trip trip = getByTripId(id); - trip.validateAuthorization(member); - return TripResponse.from(trip); - } - - @Transactional(readOnly = true) - public TripsSearchResponse readAllTrips(LoginUser loginUser) { - Member member = getById(loginUser.memberId()); - List trips = tripRepository.findAllByMemberId(member.id()); - return TripsSearchResponse.from(trips); - } - - public void updateTripById(LoginUser loginUser, Long tripId, TripUpdateRequest tripUpdateRequest) { - Member member = getById(loginUser.memberId()); - Trip trip = getByTripId(tripId); - trip.validateAuthorization(member); - - trip.changeName(tripUpdateRequest.name()); - trip.changeStatus(tripUpdateRequest.status()); - - applicationEventPublisher.publishEvent(new TripUpdateEvent(trip.id())); - } - - @Transactional(readOnly = true) - public PointResponse readPointByTripAndPointId(LoginUser loginUser, Long tripId, Long pointId) { - Member member = getById(loginUser.memberId()); - Trip trip = getByTripId(tripId); - trip.validateAuthorization(member); - - Point point = trip.findPointById(pointId); - return PointResponse.from(point); - } - - public void delete(LoginUser loginUser, Long tripId) { - Member member = getById(loginUser.memberId()); - Trip trip = getByTripId(tripId); - trip.validateAuthorization(member); - - tripRepository.delete(trip); - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/oauth/AuthTokenManager.java b/backend/src/main/java/dev/tripdraw/application/oauth/AuthTokenManager.java deleted file mode 100644 index d041aa90b..000000000 --- a/backend/src/main/java/dev/tripdraw/application/oauth/AuthTokenManager.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.tripdraw.application.oauth; - -import java.util.Date; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class AuthTokenManager { - - private final JwtTokenProvider jwtTokenProvider; - private final Long expirationTime; - - public AuthTokenManager(JwtTokenProvider jwtTokenProvider, @Value("${jwt.expiration-time}") Long expirationTime) { - this.jwtTokenProvider = jwtTokenProvider; - this.expirationTime = expirationTime; - } - - public String generate(Long memberId) { - long now = (new Date()).getTime(); - Date accessTokenExpiredAt = new Date(now + expirationTime); - - String subject = memberId.toString(); - - return jwtTokenProvider.generate(subject, accessTokenExpiredAt); - } - - public Long extractMemberId(String accessToken) { - return Long.valueOf(jwtTokenProvider.extractSubject(accessToken)); - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/oauth/JwtTokenProvider.java b/backend/src/main/java/dev/tripdraw/application/oauth/JwtTokenProvider.java deleted file mode 100644 index 8750c7cf3..000000000 --- a/backend/src/main/java/dev/tripdraw/application/oauth/JwtTokenProvider.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.tripdraw.application.oauth; - -import static dev.tripdraw.exception.auth.AuthExceptionType.EXPIRED_TOKEN; -import static dev.tripdraw.exception.auth.AuthExceptionType.INVALID_TOKEN; - -import dev.tripdraw.exception.auth.AuthException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; -import java.security.Key; -import java.util.Date; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class JwtTokenProvider { - - private final Key key; - - public JwtTokenProvider(@Value("${jwt.secret-key}") String secretKey) { - byte[] keyBytes = Decoders.BASE64.decode(secretKey); - this.key = Keys.hmacShaKeyFor(keyBytes); - } - - public String generate(String subject, Date expiredAt) { - return Jwts.builder() - .setSubject(subject) - .setExpiration(expiredAt) - .signWith(key, SignatureAlgorithm.HS512) - .compact(); - } - - public String extractSubject(String accessToken) { - Claims claims = parseClaims(accessToken); - return claims.getSubject(); - } - - private Claims parseClaims(String accessToken) { - try { - return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(accessToken) - .getBody(); - } catch (ExpiredJwtException e) { - throw new AuthException(EXPIRED_TOKEN); - } catch (JwtException e) { - throw new AuthException(INVALID_TOKEN); - } - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/oauth/OauthClient.java b/backend/src/main/java/dev/tripdraw/application/oauth/OauthClient.java deleted file mode 100644 index 3fba7c0d7..000000000 --- a/backend/src/main/java/dev/tripdraw/application/oauth/OauthClient.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.tripdraw.application.oauth; - -import dev.tripdraw.domain.oauth.OauthType; -import dev.tripdraw.dto.auth.OauthInfo; - -public interface OauthClient { - - OauthType oauthType(); - - OauthInfo requestOauthInfo(String accessToken); -} diff --git a/backend/src/main/java/dev/tripdraw/presentation/member/AuthExtractor.java b/backend/src/main/java/dev/tripdraw/auth/application/AuthExtractor.java similarity index 75% rename from backend/src/main/java/dev/tripdraw/presentation/member/AuthExtractor.java rename to backend/src/main/java/dev/tripdraw/auth/application/AuthExtractor.java index 0c340e1c0..fb3273b8c 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/member/AuthExtractor.java +++ b/backend/src/main/java/dev/tripdraw/auth/application/AuthExtractor.java @@ -1,12 +1,11 @@ -package dev.tripdraw.presentation.member; +package dev.tripdraw.auth.application; -import static dev.tripdraw.exception.auth.AuthExceptionType.INVALID_AUTH_HEADER; +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_AUTH_HEADER; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.util.StringUtils.hasText; -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.exception.auth.AuthException; +import dev.tripdraw.auth.exception.AuthException; +import dev.tripdraw.common.auth.LoginUser; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -18,7 +17,7 @@ public class AuthExtractor { private static final String AUTHORIZATION_PREFIX = "Bearer "; private static final String EMPTY = ""; - private final AuthTokenManager authTokenManager; + private final JwtTokenProvider jwtTokenProvider; public LoginUser extract(HttpServletRequest request) { Long memberId = parse(request); @@ -39,6 +38,6 @@ private static void validate(String header) { private Long parseCredential(String header) { String credential = header.replace(AUTHORIZATION_PREFIX, EMPTY); - return authTokenManager.extractMemberId(credential); + return Long.valueOf(jwtTokenProvider.extractAccessToken(credential)); } } diff --git a/backend/src/main/java/dev/tripdraw/auth/application/AuthFacadeService.java b/backend/src/main/java/dev/tripdraw/auth/application/AuthFacadeService.java new file mode 100644 index 000000000..30a65a004 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/application/AuthFacadeService.java @@ -0,0 +1,38 @@ +package dev.tripdraw.auth.application; + +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.auth.dto.OauthRequest; +import dev.tripdraw.auth.dto.OauthResponse; +import dev.tripdraw.auth.dto.RegisterRequest; +import dev.tripdraw.auth.dto.TokenRefreshRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +@RequiredArgsConstructor +@Service +public class AuthFacadeService { + + private final OAuthService oAuthService; + private final AuthService authService; + private final TokenGenerateService tokenGenerateService; + private final TransactionTemplate transactionTemplate; + + public OauthResponse login(OauthRequest oauthRequest) { + OauthInfo oauthInfo = oAuthService.request(oauthRequest.oauthType(), oauthRequest.oauthToken()); + return transactionTemplate.execute(status -> authService.login(oauthInfo) + .map(member -> tokenGenerateService.generate(member.id())) + .orElseGet(tokenGenerateService::generateEmptyToken)); + } + + public OauthResponse register(RegisterRequest registerRequest) { + OauthInfo oauthInfo = oAuthService.request(registerRequest.oauthType(), registerRequest.oauthToken()); + return transactionTemplate.execute(status -> authService.register(oauthInfo, registerRequest.nickname()) + .map(member -> tokenGenerateService.generate(member.id())) + .orElseGet(tokenGenerateService::generateEmptyToken)); + } + + public OauthResponse refresh(TokenRefreshRequest tokenRefreshRequest) { + return tokenGenerateService.refresh(tokenRefreshRequest.refreshToken()); + } +} diff --git a/backend/src/main/java/dev/tripdraw/auth/application/AuthService.java b/backend/src/main/java/dev/tripdraw/auth/application/AuthService.java new file mode 100644 index 000000000..dc75fcbc7 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/application/AuthService.java @@ -0,0 +1,40 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.member.exception.MemberExceptionType.DUPLICATE_NICKNAME; +import static dev.tripdraw.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; + +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.exception.MemberException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class AuthService { + + private final MemberRepository memberRepository; + + public Optional login(OauthInfo oauthInfo) { + return memberRepository.findByOauthIdAndOauthType(oauthInfo.oauthId(), oauthInfo.oauthType()) + .or(() -> { + memberRepository.save(Member.of(oauthInfo.oauthId(), oauthInfo.oauthType())); + return Optional.empty(); + }); + } + + public Optional register(OauthInfo oauthInfo, String nickname) { + if (memberRepository.existsByNickname(nickname)) { + throw new MemberException(DUPLICATE_NICKNAME); + } + + Member member = memberRepository.findByOauthIdAndOauthType(oauthInfo.oauthId(), oauthInfo.oauthType()) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + member.changeNickname(nickname); + return Optional.of(member); + } +} diff --git a/backend/src/main/java/dev/tripdraw/auth/application/JwtTokenProvider.java b/backend/src/main/java/dev/tripdraw/auth/application/JwtTokenProvider.java new file mode 100644 index 000000000..fa7664478 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/application/JwtTokenProvider.java @@ -0,0 +1,86 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.auth.exception.AuthExceptionType.EXPIRED_ACCESS_TOKEN; +import static dev.tripdraw.auth.exception.AuthExceptionType.EXPIRED_REFRESH_TOKEN; +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_TOKEN; + +import dev.tripdraw.auth.config.AccessTokenConfig; +import dev.tripdraw.auth.config.RefreshTokenConfig; +import dev.tripdraw.auth.exception.AuthException; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.security.Key; +import java.util.Date; +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class JwtTokenProvider { + + private final Key accessKey; + private final Long accessTokenExpirationTime; + private final Key refreshKey; + private final Long refreshTokenExpirationTime; + + public JwtTokenProvider(AccessTokenConfig accessTokenConfig, RefreshTokenConfig refreshTokenConfig) { + this.accessKey = generateKey(accessTokenConfig.secretKey()); + this.accessTokenExpirationTime = accessTokenConfig.expirationTime(); + this.refreshKey = generateKey(refreshTokenConfig.secretKey()); + this.refreshTokenExpirationTime = refreshTokenConfig.expirationTime(); + } + + private Key generateKey(String secret) { + byte[] accessKeyBytes = Decoders.BASE64.decode(secret); + return Keys.hmacShaKeyFor(accessKeyBytes); + } + + public String generateAccessToken(String subject) { + return generateToken(subject, accessTokenExpirationTime, accessKey); + } + + private String generateToken(String subject, Long expirationTime, Key key) { + final Date now = new Date(); + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + expirationTime)) + .signWith(key, SignatureAlgorithm.HS512) + .compact(); + } + + public String generateRefreshToken() { + return generateToken(UUID.randomUUID().toString(), refreshTokenExpirationTime, refreshKey); + } + + public String extractAccessToken(String accessToken) { + try { + return Jwts.parserBuilder() + .setSigningKey(accessKey) + .build() + .parseClaimsJws(accessToken) + .getBody() + .getSubject(); + } catch (ExpiredJwtException e) { + throw new AuthException(EXPIRED_ACCESS_TOKEN); + } catch (JwtException e) { + throw new AuthException(INVALID_TOKEN); + } + } + + public void validateRefreshToken(String refreshToken) { + try { + Jwts.parserBuilder() + .setSigningKey(refreshKey) + .build() + .parseClaimsJws(refreshToken); + } catch (ExpiredJwtException e) { + throw new AuthException(EXPIRED_REFRESH_TOKEN); + } catch (JwtException e) { + throw new AuthException(INVALID_TOKEN); + } + } +} diff --git a/backend/src/main/java/dev/tripdraw/auth/application/OAuthService.java b/backend/src/main/java/dev/tripdraw/auth/application/OAuthService.java new file mode 100644 index 000000000..3356a2c5e --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/application/OAuthService.java @@ -0,0 +1,20 @@ +package dev.tripdraw.auth.application; + +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.auth.oauth.OauthClient; +import dev.tripdraw.auth.oauth.OauthClientProvider; +import dev.tripdraw.common.auth.OauthType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class OAuthService { + + private final OauthClientProvider oauthClientProvider; + + public OauthInfo request(OauthType oauthType, String oauthToken) { + OauthClient oauthClient = oauthClientProvider.provide(oauthType); + return oauthClient.requestOauthInfo(oauthToken); + } +} diff --git a/backend/src/main/java/dev/tripdraw/auth/application/TokenGenerateService.java b/backend/src/main/java/dev/tripdraw/auth/application/TokenGenerateService.java new file mode 100644 index 000000000..ff1e088f8 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/application/TokenGenerateService.java @@ -0,0 +1,40 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_TOKEN; + +import dev.tripdraw.auth.domain.RefreshToken; +import dev.tripdraw.auth.domain.RefreshTokenRepository; +import dev.tripdraw.auth.dto.OauthResponse; +import dev.tripdraw.auth.exception.AuthException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class TokenGenerateService { + + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenRepository refreshTokenRepository; + + @Transactional + public OauthResponse refresh(String token) { + jwtTokenProvider.validateRefreshToken(token); + RefreshToken refreshToken = refreshTokenRepository.findByToken(token) + .orElseThrow(() -> new AuthException(INVALID_TOKEN)); + return generate(refreshToken.memberId()); + } + + @Transactional + public OauthResponse generate(Long memberId) { + String accessToken = jwtTokenProvider.generateAccessToken(memberId.toString()); + String refreshToken = jwtTokenProvider.generateRefreshToken(); + refreshTokenRepository.deleteByMemberId(memberId); + refreshTokenRepository.save(new RefreshToken(memberId, refreshToken)); + return new OauthResponse(accessToken, refreshToken); + } + + public OauthResponse generateEmptyToken() { + return new OauthResponse("", ""); + } +} diff --git a/backend/src/main/java/dev/tripdraw/auth/config/AccessTokenConfig.java b/backend/src/main/java/dev/tripdraw/auth/config/AccessTokenConfig.java new file mode 100644 index 000000000..fe4dc8d1c --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/config/AccessTokenConfig.java @@ -0,0 +1,10 @@ +package dev.tripdraw.auth.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt.access") +public record AccessTokenConfig( + String secretKey, + Long expirationTime +) { +} diff --git a/backend/src/main/java/dev/tripdraw/auth/config/RefreshTokenConfig.java b/backend/src/main/java/dev/tripdraw/auth/config/RefreshTokenConfig.java new file mode 100644 index 000000000..c3f05dc88 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/config/RefreshTokenConfig.java @@ -0,0 +1,10 @@ +package dev.tripdraw.auth.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "jwt.refresh") +public record RefreshTokenConfig( + String secretKey, + Long expirationTime +) { +} diff --git a/backend/src/main/java/dev/tripdraw/auth/domain/RefreshToken.java b/backend/src/main/java/dev/tripdraw/auth/domain/RefreshToken.java new file mode 100644 index 000000000..7b393e55d --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/domain/RefreshToken.java @@ -0,0 +1,39 @@ +package dev.tripdraw.auth.domain; + +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import dev.tripdraw.common.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Accessors(fluent = true) +@Getter +@NoArgsConstructor(access = PROTECTED) +@Entity +public class RefreshToken extends BaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "refresh_token_id") + private Long id; + + private Long memberId; + + private String token; + + public RefreshToken(Long memberId, String token) { + this(null, memberId, token); + } + + public RefreshToken(Long id, Long memberId, String token) { + this.id = id; + this.memberId = memberId; + this.token = token; + } +} diff --git a/backend/src/main/java/dev/tripdraw/auth/domain/RefreshTokenRepository.java b/backend/src/main/java/dev/tripdraw/auth/domain/RefreshTokenRepository.java new file mode 100644 index 000000000..15fff325c --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/domain/RefreshTokenRepository.java @@ -0,0 +1,11 @@ +package dev.tripdraw.auth.domain; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RefreshTokenRepository extends JpaRepository { + + Optional findByToken(String token); + + void deleteByMemberId(Long memberId); +} diff --git a/backend/src/main/java/dev/tripdraw/dto/auth/KakaoInfoResponse.java b/backend/src/main/java/dev/tripdraw/auth/dto/KakaoInfoResponse.java similarity index 94% rename from backend/src/main/java/dev/tripdraw/dto/auth/KakaoInfoResponse.java rename to backend/src/main/java/dev/tripdraw/auth/dto/KakaoInfoResponse.java index c15541d54..ef99336de 100644 --- a/backend/src/main/java/dev/tripdraw/dto/auth/KakaoInfoResponse.java +++ b/backend/src/main/java/dev/tripdraw/auth/dto/KakaoInfoResponse.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.auth; +package dev.tripdraw.auth.dto; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; diff --git a/backend/src/main/java/dev/tripdraw/auth/dto/OauthInfo.java b/backend/src/main/java/dev/tripdraw/auth/dto/OauthInfo.java new file mode 100644 index 000000000..78135316c --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/dto/OauthInfo.java @@ -0,0 +1,6 @@ +package dev.tripdraw.auth.dto; + +import dev.tripdraw.common.auth.OauthType; + +public record OauthInfo(String oauthId, OauthType oauthType) { +} diff --git a/backend/src/main/java/dev/tripdraw/dto/auth/OauthRequest.java b/backend/src/main/java/dev/tripdraw/auth/dto/OauthRequest.java similarity index 86% rename from backend/src/main/java/dev/tripdraw/dto/auth/OauthRequest.java rename to backend/src/main/java/dev/tripdraw/auth/dto/OauthRequest.java index 010015716..c2d79d4fe 100644 --- a/backend/src/main/java/dev/tripdraw/dto/auth/OauthRequest.java +++ b/backend/src/main/java/dev/tripdraw/auth/dto/OauthRequest.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.auth; +package dev.tripdraw.auth.dto; -import dev.tripdraw.domain.oauth.OauthType; +import dev.tripdraw.common.auth.OauthType; import io.swagger.v3.oas.annotations.media.Schema; public record OauthRequest( @@ -11,6 +11,5 @@ public record OauthRequest( @Schema(description = "Access Token", example = TOKEN_SAMPLE) String oauthToken ) { - private static final String TOKEN_SAMPLE = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzMiLCJleHAiOjE2OTE4OTA0NjZ9.NlzZEEDWdjovafRPCD1ZvddeRUccZfyZpcUWzGPAI4oK-PKyPM64TIMJ3HyOy29vJtg_MET1c4omWUkGOb4qyQ"; } diff --git a/backend/src/main/java/dev/tripdraw/dto/auth/OauthResponse.java b/backend/src/main/java/dev/tripdraw/auth/dto/OauthResponse.java similarity index 69% rename from backend/src/main/java/dev/tripdraw/dto/auth/OauthResponse.java rename to backend/src/main/java/dev/tripdraw/auth/dto/OauthResponse.java index 345be6832..2f244b2e0 100644 --- a/backend/src/main/java/dev/tripdraw/dto/auth/OauthResponse.java +++ b/backend/src/main/java/dev/tripdraw/auth/dto/OauthResponse.java @@ -1,11 +1,14 @@ -package dev.tripdraw.dto.auth; +package dev.tripdraw.auth.dto; import io.swagger.v3.oas.annotations.media.Schema; public record OauthResponse( + @Schema(description = "Access Token", example = TOKEN_SAMPLE) + String accessToken, - String accessToken + @Schema(description = "Refresh Token", example = TOKEN_SAMPLE) + String refreshToken ) { private static final String TOKEN_SAMPLE = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzMiLCJleHAiOjE2OTE4OTA0NjZ9.NlzZEEDWdjovafRPCD1ZvddeRUccZfyZpcUWzGPAI4oK-PKyPM64TIMJ3HyOy29vJtg_MET1c4omWUkGOb4qyQ"; } diff --git a/backend/src/main/java/dev/tripdraw/dto/auth/RegisterRequest.java b/backend/src/main/java/dev/tripdraw/auth/dto/RegisterRequest.java similarity index 83% rename from backend/src/main/java/dev/tripdraw/dto/auth/RegisterRequest.java rename to backend/src/main/java/dev/tripdraw/auth/dto/RegisterRequest.java index 9594a7210..4d94c2330 100644 --- a/backend/src/main/java/dev/tripdraw/dto/auth/RegisterRequest.java +++ b/backend/src/main/java/dev/tripdraw/auth/dto/RegisterRequest.java @@ -1,7 +1,7 @@ -package dev.tripdraw.dto.auth; +package dev.tripdraw.auth.dto; -import dev.tripdraw.domain.oauth.OauthType; -import dev.tripdraw.dto.validation.NoWhiteSpace; +import dev.tripdraw.common.auth.OauthType; +import dev.tripdraw.common.validation.NoWhiteSpace; import io.swagger.v3.oas.annotations.media.Schema; public record RegisterRequest( diff --git a/backend/src/main/java/dev/tripdraw/auth/dto/TokenRefreshRequest.java b/backend/src/main/java/dev/tripdraw/auth/dto/TokenRefreshRequest.java new file mode 100644 index 000000000..9aedc144e --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/dto/TokenRefreshRequest.java @@ -0,0 +1,11 @@ +package dev.tripdraw.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record TokenRefreshRequest( + + @Schema(description = "Refresh Token", example = TOKEN_SAMPLE) + String refreshToken +) { + private static final String TOKEN_SAMPLE = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzMiLCJleHAiOjE2OTE4OTA0NjZ9.NlzZEEDWdjovafRPCD1ZvddeRUccZfyZpcUWzGPAI4oK-PKyPM64TIMJ3HyOy29vJtg_MET1c4omWUkGOb4qyQ"; +} diff --git a/backend/src/main/java/dev/tripdraw/exception/auth/AuthException.java b/backend/src/main/java/dev/tripdraw/auth/exception/AuthException.java similarity index 51% rename from backend/src/main/java/dev/tripdraw/exception/auth/AuthException.java rename to backend/src/main/java/dev/tripdraw/auth/exception/AuthException.java index b92c11a02..e5839e24d 100644 --- a/backend/src/main/java/dev/tripdraw/exception/auth/AuthException.java +++ b/backend/src/main/java/dev/tripdraw/auth/exception/AuthException.java @@ -1,7 +1,7 @@ -package dev.tripdraw.exception.auth; +package dev.tripdraw.auth.exception; -import dev.tripdraw.exception.common.BaseException; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.BaseException; +import dev.tripdraw.common.exception.ExceptionType; public class AuthException extends BaseException { diff --git a/backend/src/main/java/dev/tripdraw/exception/auth/AuthExceptionType.java b/backend/src/main/java/dev/tripdraw/auth/exception/AuthExceptionType.java similarity index 78% rename from backend/src/main/java/dev/tripdraw/exception/auth/AuthExceptionType.java rename to backend/src/main/java/dev/tripdraw/auth/exception/AuthExceptionType.java index 0ffda0bc3..9e6440d6f 100644 --- a/backend/src/main/java/dev/tripdraw/exception/auth/AuthExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/auth/exception/AuthExceptionType.java @@ -1,16 +1,17 @@ -package dev.tripdraw.exception.auth; +package dev.tripdraw.auth.exception; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.UNAUTHORIZED; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.ExceptionType; import org.springframework.http.HttpStatus; public enum AuthExceptionType implements ExceptionType { INVALID_AUTH_HEADER(BAD_REQUEST, "Authoriztion 헤더가 올바르지 않습니다."), AUTH_FAIL(FORBIDDEN, "접근 권한이 없습니다."), - EXPIRED_TOKEN(UNAUTHORIZED, "만료된 토큰입니다."), + EXPIRED_ACCESS_TOKEN(UNAUTHORIZED, "만료된 엑세스 토큰입니다."), + EXPIRED_REFRESH_TOKEN(UNAUTHORIZED, "만료된 리프레시 토큰입니다."), INVALID_TOKEN(UNAUTHORIZED, "유효하지 않은 토큰입니다."), ; diff --git a/backend/src/main/java/dev/tripdraw/application/oauth/KakaoApiClient.java b/backend/src/main/java/dev/tripdraw/auth/oauth/KakaoApiClient.java similarity index 88% rename from backend/src/main/java/dev/tripdraw/application/oauth/KakaoApiClient.java rename to backend/src/main/java/dev/tripdraw/auth/oauth/KakaoApiClient.java index 68e1678cb..88e4358b5 100644 --- a/backend/src/main/java/dev/tripdraw/application/oauth/KakaoApiClient.java +++ b/backend/src/main/java/dev/tripdraw/auth/oauth/KakaoApiClient.java @@ -1,11 +1,11 @@ -package dev.tripdraw.application.oauth; +package dev.tripdraw.auth.oauth; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; -import dev.tripdraw.domain.oauth.OauthType; -import dev.tripdraw.dto.auth.KakaoInfoResponse; -import dev.tripdraw.dto.auth.OauthInfo; +import dev.tripdraw.auth.dto.KakaoInfoResponse; +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.common.auth.OauthType; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -16,7 +16,7 @@ public class KakaoApiClient implements OauthClient { private static final String BEARER = "Bearer "; - + private final String kakaoInfoUrl; private final RestTemplate restTemplate; diff --git a/backend/src/main/java/dev/tripdraw/auth/oauth/OauthClient.java b/backend/src/main/java/dev/tripdraw/auth/oauth/OauthClient.java new file mode 100644 index 000000000..0792963cf --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/auth/oauth/OauthClient.java @@ -0,0 +1,11 @@ +package dev.tripdraw.auth.oauth; + +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.common.auth.OauthType; + +public interface OauthClient { + + OauthType oauthType(); + + OauthInfo requestOauthInfo(String accessToken); +} diff --git a/backend/src/main/java/dev/tripdraw/application/oauth/OauthClientProvider.java b/backend/src/main/java/dev/tripdraw/auth/oauth/OauthClientProvider.java similarity index 87% rename from backend/src/main/java/dev/tripdraw/application/oauth/OauthClientProvider.java rename to backend/src/main/java/dev/tripdraw/auth/oauth/OauthClientProvider.java index bc9421ab2..877d52df1 100644 --- a/backend/src/main/java/dev/tripdraw/application/oauth/OauthClientProvider.java +++ b/backend/src/main/java/dev/tripdraw/auth/oauth/OauthClientProvider.java @@ -1,9 +1,9 @@ -package dev.tripdraw.application.oauth; +package dev.tripdraw.auth.oauth; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; -import dev.tripdraw.domain.oauth.OauthType; +import dev.tripdraw.common.auth.OauthType; import java.util.Map; import java.util.Set; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/dev/tripdraw/presentation/member/AuthArgumentResolver.java b/backend/src/main/java/dev/tripdraw/auth/presentation/AuthArgumentResolver.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/presentation/member/AuthArgumentResolver.java rename to backend/src/main/java/dev/tripdraw/auth/presentation/AuthArgumentResolver.java index b95d42285..03e48e7e7 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/member/AuthArgumentResolver.java +++ b/backend/src/main/java/dev/tripdraw/auth/presentation/AuthArgumentResolver.java @@ -1,5 +1,7 @@ -package dev.tripdraw.presentation.member; +package dev.tripdraw.auth.presentation; +import dev.tripdraw.auth.application.AuthExtractor; +import dev.tripdraw.common.auth.Auth; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; diff --git a/backend/src/main/java/dev/tripdraw/presentation/controller/AuthController.java b/backend/src/main/java/dev/tripdraw/auth/presentation/AuthController.java similarity index 57% rename from backend/src/main/java/dev/tripdraw/presentation/controller/AuthController.java rename to backend/src/main/java/dev/tripdraw/auth/presentation/AuthController.java index 676ea5be1..8a6e313f0 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/controller/AuthController.java +++ b/backend/src/main/java/dev/tripdraw/auth/presentation/AuthController.java @@ -1,9 +1,10 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.auth.presentation; -import dev.tripdraw.application.AuthService; -import dev.tripdraw.dto.auth.OauthRequest; -import dev.tripdraw.dto.auth.OauthResponse; -import dev.tripdraw.dto.auth.RegisterRequest; +import dev.tripdraw.auth.application.AuthFacadeService; +import dev.tripdraw.auth.dto.OauthRequest; +import dev.tripdraw.auth.dto.OauthResponse; +import dev.tripdraw.auth.dto.RegisterRequest; +import dev.tripdraw.auth.dto.TokenRefreshRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,7 +20,7 @@ @RestController public class AuthController { - private final AuthService authService; + private final AuthFacadeService authFacadeService; @Operation(summary = "소셜 로그인 API", description = "소셜 로그인을 합니다.") @ApiResponse( @@ -28,7 +29,7 @@ public class AuthController { ) @PostMapping("/oauth/login") public ResponseEntity login(@RequestBody OauthRequest oauthRequest) { - OauthResponse oauthResponse = authService.login(oauthRequest); + OauthResponse oauthResponse = authFacadeService.login(oauthRequest); return ResponseEntity.ok(oauthResponse); } @@ -39,7 +40,18 @@ public ResponseEntity login(@RequestBody OauthRequest oauthReques ) @PostMapping("/oauth/register") public ResponseEntity register(@Valid @RequestBody RegisterRequest registerRequest) { - OauthResponse oauthResponse = authService.register(registerRequest); + OauthResponse oauthResponse = authFacadeService.register(registerRequest); + return ResponseEntity.ok(oauthResponse); + } + + @Operation(summary = "토큰 재발급 API", description = "리프레쉬 토큰을 이용하여 토큰을 재발급합니다.") + @ApiResponse( + responseCode = "200", + description = "토큰 재발급 성공." + ) + @PostMapping("/oauth/refresh") + public ResponseEntity refresh(@Valid @RequestBody TokenRefreshRequest tokenRefreshRequest) { + OauthResponse oauthResponse = authFacadeService.refresh(tokenRefreshRequest); return ResponseEntity.ok(oauthResponse); } } diff --git a/backend/src/main/java/dev/tripdraw/presentation/member/AuthInterceptor.java b/backend/src/main/java/dev/tripdraw/auth/presentation/AuthInterceptor.java similarity index 73% rename from backend/src/main/java/dev/tripdraw/presentation/member/AuthInterceptor.java rename to backend/src/main/java/dev/tripdraw/auth/presentation/AuthInterceptor.java index 9c7cbac55..dded01696 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/member/AuthInterceptor.java +++ b/backend/src/main/java/dev/tripdraw/auth/presentation/AuthInterceptor.java @@ -1,10 +1,11 @@ -package dev.tripdraw.presentation.member; +package dev.tripdraw.auth.presentation; -import static dev.tripdraw.exception.auth.AuthExceptionType.AUTH_FAIL; +import static dev.tripdraw.auth.exception.AuthExceptionType.AUTH_FAIL; -import dev.tripdraw.application.MemberService; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.exception.auth.AuthException; +import dev.tripdraw.auth.application.AuthExtractor; +import dev.tripdraw.auth.exception.AuthException; +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.member.application.MemberService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/dev/tripdraw/presentation/member/Auth.java b/backend/src/main/java/dev/tripdraw/common/auth/Auth.java similarity index 86% rename from backend/src/main/java/dev/tripdraw/presentation/member/Auth.java rename to backend/src/main/java/dev/tripdraw/common/auth/Auth.java index e78ec36e4..89a02d0ee 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/member/Auth.java +++ b/backend/src/main/java/dev/tripdraw/common/auth/Auth.java @@ -1,4 +1,4 @@ -package dev.tripdraw.presentation.member; +package dev.tripdraw.common.auth; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/backend/src/main/java/dev/tripdraw/dto/auth/LoginUser.java b/backend/src/main/java/dev/tripdraw/common/auth/LoginUser.java similarity index 74% rename from backend/src/main/java/dev/tripdraw/dto/auth/LoginUser.java rename to backend/src/main/java/dev/tripdraw/common/auth/LoginUser.java index 68110ee5d..442e7cc3c 100644 --- a/backend/src/main/java/dev/tripdraw/dto/auth/LoginUser.java +++ b/backend/src/main/java/dev/tripdraw/common/auth/LoginUser.java @@ -1,4 +1,4 @@ -package dev.tripdraw.dto.auth; +package dev.tripdraw.common.auth; import io.swagger.v3.oas.annotations.Hidden; diff --git a/backend/src/main/java/dev/tripdraw/domain/oauth/OauthType.java b/backend/src/main/java/dev/tripdraw/common/auth/OauthType.java similarity index 55% rename from backend/src/main/java/dev/tripdraw/domain/oauth/OauthType.java rename to backend/src/main/java/dev/tripdraw/common/auth/OauthType.java index eed4dd1b0..5def98842 100644 --- a/backend/src/main/java/dev/tripdraw/domain/oauth/OauthType.java +++ b/backend/src/main/java/dev/tripdraw/common/auth/OauthType.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.oauth; +package dev.tripdraw.common.auth; public enum OauthType { KAKAO, diff --git a/backend/src/main/java/dev/tripdraw/config/AsyncConfig.java b/backend/src/main/java/dev/tripdraw/common/config/AsyncConfig.java similarity index 83% rename from backend/src/main/java/dev/tripdraw/config/AsyncConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/AsyncConfig.java index 6bfe04d56..ec9efdbb3 100644 --- a/backend/src/main/java/dev/tripdraw/config/AsyncConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/AsyncConfig.java @@ -1,4 +1,4 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; diff --git a/backend/src/main/java/dev/tripdraw/config/AuthConfig.java b/backend/src/main/java/dev/tripdraw/common/config/AuthConfig.java similarity index 81% rename from backend/src/main/java/dev/tripdraw/config/AuthConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/AuthConfig.java index f039ff7dc..b72575681 100644 --- a/backend/src/main/java/dev/tripdraw/config/AuthConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/AuthConfig.java @@ -1,8 +1,8 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; -import dev.tripdraw.presentation.member.AuthArgumentResolver; -import dev.tripdraw.presentation.member.AuthExtractor; -import dev.tripdraw.presentation.member.AuthInterceptor; +import dev.tripdraw.auth.application.AuthExtractor; +import dev.tripdraw.auth.presentation.AuthArgumentResolver; +import dev.tripdraw.auth.presentation.AuthInterceptor; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; @@ -25,7 +25,6 @@ public void addArgumentResolvers(List resolvers) @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) - .excludePathPatterns("/members/**") .excludePathPatterns("/swagger-ui/**") .excludePathPatterns("/api-docs") .excludePathPatterns("/v3/api-docs/**") diff --git a/backend/src/main/java/dev/tripdraw/config/ClientConfig.java b/backend/src/main/java/dev/tripdraw/common/config/ClientConfig.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/config/ClientConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/ClientConfig.java index 0b224145f..d02add7da 100644 --- a/backend/src/main/java/dev/tripdraw/config/ClientConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/ClientConfig.java @@ -1,4 +1,4 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/backend/src/main/java/dev/tripdraw/config/FilterConfig.java b/backend/src/main/java/dev/tripdraw/common/config/FilterConfig.java similarity index 86% rename from backend/src/main/java/dev/tripdraw/config/FilterConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/FilterConfig.java index 04e1eb5c0..6ada66ec8 100644 --- a/backend/src/main/java/dev/tripdraw/config/FilterConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/FilterConfig.java @@ -1,8 +1,8 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; -import dev.tripdraw.common.LogFilter; -import dev.tripdraw.common.MdcFilter; -import dev.tripdraw.common.QueryCounter; +import dev.tripdraw.common.log.LogFilter; +import dev.tripdraw.common.log.MdcFilter; +import dev.tripdraw.common.log.QueryCounter; import lombok.RequiredArgsConstructor; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; diff --git a/backend/src/main/java/dev/tripdraw/config/HibernateConfig.java b/backend/src/main/java/dev/tripdraw/common/config/HibernateConfig.java similarity index 88% rename from backend/src/main/java/dev/tripdraw/config/HibernateConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/HibernateConfig.java index 6614ec4de..371708a24 100644 --- a/backend/src/main/java/dev/tripdraw/config/HibernateConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/HibernateConfig.java @@ -1,8 +1,8 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; import static org.hibernate.cfg.AvailableSettings.STATEMENT_INSPECTOR; -import dev.tripdraw.common.QueryInspector; +import dev.tripdraw.common.log.QueryInspector; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; import org.springframework.context.annotation.Bean; diff --git a/backend/src/main/java/dev/tripdraw/config/JpaConfig.java b/backend/src/main/java/dev/tripdraw/common/config/JpaConfig.java similarity index 84% rename from backend/src/main/java/dev/tripdraw/config/JpaConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/JpaConfig.java index 4a30c6f60..029d5edb8 100644 --- a/backend/src/main/java/dev/tripdraw/config/JpaConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/JpaConfig.java @@ -1,4 +1,4 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/backend/src/main/java/dev/tripdraw/config/MultipartConfig.java b/backend/src/main/java/dev/tripdraw/common/config/MultipartConfig.java similarity index 96% rename from backend/src/main/java/dev/tripdraw/config/MultipartConfig.java rename to backend/src/main/java/dev/tripdraw/common/config/MultipartConfig.java index 1f0945cb6..969e4503d 100644 --- a/backend/src/main/java/dev/tripdraw/config/MultipartConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/config/MultipartConfig.java @@ -1,4 +1,4 @@ -package dev.tripdraw.config; +package dev.tripdraw.common.config; import static org.springframework.util.unit.DataUnit.MEGABYTES; diff --git a/backend/src/main/java/dev/tripdraw/common/config/QueryDslConfig.java b/backend/src/main/java/dev/tripdraw/common/config/QueryDslConfig.java new file mode 100644 index 000000000..9df83f7ab --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/common/config/QueryDslConfig.java @@ -0,0 +1,19 @@ +package dev.tripdraw.common.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QueryDslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/backend/src/main/java/dev/tripdraw/domain/common/BaseEntity.java b/backend/src/main/java/dev/tripdraw/common/entity/BaseEntity.java similarity index 94% rename from backend/src/main/java/dev/tripdraw/domain/common/BaseEntity.java rename to backend/src/main/java/dev/tripdraw/common/entity/BaseEntity.java index 7b1ab624b..e41183d78 100644 --- a/backend/src/main/java/dev/tripdraw/domain/common/BaseEntity.java +++ b/backend/src/main/java/dev/tripdraw/common/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.common; +package dev.tripdraw.common.entity; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; diff --git a/backend/src/main/java/dev/tripdraw/exception/common/BaseException.java b/backend/src/main/java/dev/tripdraw/common/exception/BaseException.java similarity index 90% rename from backend/src/main/java/dev/tripdraw/exception/common/BaseException.java rename to backend/src/main/java/dev/tripdraw/common/exception/BaseException.java index ea8ffbeb1..6a406449d 100644 --- a/backend/src/main/java/dev/tripdraw/exception/common/BaseException.java +++ b/backend/src/main/java/dev/tripdraw/common/exception/BaseException.java @@ -1,4 +1,4 @@ -package dev.tripdraw.exception.common; +package dev.tripdraw.common.exception; public class BaseException extends RuntimeException { diff --git a/backend/src/main/java/dev/tripdraw/exception/common/ExceptionResponse.java b/backend/src/main/java/dev/tripdraw/common/exception/ExceptionResponse.java similarity index 93% rename from backend/src/main/java/dev/tripdraw/exception/common/ExceptionResponse.java rename to backend/src/main/java/dev/tripdraw/common/exception/ExceptionResponse.java index 397623952..784b7b09c 100644 --- a/backend/src/main/java/dev/tripdraw/exception/common/ExceptionResponse.java +++ b/backend/src/main/java/dev/tripdraw/common/exception/ExceptionResponse.java @@ -1,4 +1,4 @@ -package dev.tripdraw.exception.common; +package dev.tripdraw.common.exception; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; diff --git a/backend/src/main/java/dev/tripdraw/exception/common/ExceptionType.java b/backend/src/main/java/dev/tripdraw/common/exception/ExceptionType.java similarity index 79% rename from backend/src/main/java/dev/tripdraw/exception/common/ExceptionType.java rename to backend/src/main/java/dev/tripdraw/common/exception/ExceptionType.java index a9517065b..1e96dd306 100644 --- a/backend/src/main/java/dev/tripdraw/exception/common/ExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/common/exception/ExceptionType.java @@ -1,4 +1,4 @@ -package dev.tripdraw.exception.common; +package dev.tripdraw.common.exception; import org.springframework.http.HttpStatus; diff --git a/backend/src/main/java/dev/tripdraw/exception/common/GlobalExceptionHandler.java b/backend/src/main/java/dev/tripdraw/common/exception/GlobalExceptionHandler.java similarity index 96% rename from backend/src/main/java/dev/tripdraw/exception/common/GlobalExceptionHandler.java rename to backend/src/main/java/dev/tripdraw/common/exception/GlobalExceptionHandler.java index a4314c372..f4ce42272 100644 --- a/backend/src/main/java/dev/tripdraw/exception/common/GlobalExceptionHandler.java +++ b/backend/src/main/java/dev/tripdraw/common/exception/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ -package dev.tripdraw.exception.common; +package dev.tripdraw.common.exception; -import static dev.tripdraw.common.MdcToken.REQUEST_ID; +import static dev.tripdraw.common.log.MdcToken.REQUEST_ID; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; diff --git a/backend/src/main/java/dev/tripdraw/exception/common/ValidationException.java b/backend/src/main/java/dev/tripdraw/common/exception/ValidationException.java similarity index 88% rename from backend/src/main/java/dev/tripdraw/exception/common/ValidationException.java rename to backend/src/main/java/dev/tripdraw/common/exception/ValidationException.java index cb8e025bc..1d305a5c4 100644 --- a/backend/src/main/java/dev/tripdraw/exception/common/ValidationException.java +++ b/backend/src/main/java/dev/tripdraw/common/exception/ValidationException.java @@ -1,4 +1,4 @@ -package dev.tripdraw.exception.common; +package dev.tripdraw.common.exception; import org.springframework.validation.FieldError; diff --git a/backend/src/main/java/dev/tripdraw/common/LogFilter.java b/backend/src/main/java/dev/tripdraw/common/log/LogFilter.java similarity index 94% rename from backend/src/main/java/dev/tripdraw/common/LogFilter.java rename to backend/src/main/java/dev/tripdraw/common/log/LogFilter.java index bf3b758b0..7bebfda9c 100644 --- a/backend/src/main/java/dev/tripdraw/common/LogFilter.java +++ b/backend/src/main/java/dev/tripdraw/common/log/LogFilter.java @@ -1,6 +1,6 @@ -package dev.tripdraw.common; +package dev.tripdraw.common.log; -import static dev.tripdraw.common.MdcToken.REQUEST_ID; +import static dev.tripdraw.common.log.MdcToken.REQUEST_ID; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; @@ -34,7 +34,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha long end = System.currentTimeMillis(); log(httpServletRequest.getRequestURI(), httpServletRequest.getMethod(), end - start); queryCounter.close(); - } private void log(String uri, String method, long time) { diff --git a/backend/src/main/java/dev/tripdraw/common/MdcFilter.java b/backend/src/main/java/dev/tripdraw/common/log/MdcFilter.java similarity index 86% rename from backend/src/main/java/dev/tripdraw/common/MdcFilter.java rename to backend/src/main/java/dev/tripdraw/common/log/MdcFilter.java index ecd9caa81..9fd46b285 100644 --- a/backend/src/main/java/dev/tripdraw/common/MdcFilter.java +++ b/backend/src/main/java/dev/tripdraw/common/log/MdcFilter.java @@ -1,6 +1,6 @@ -package dev.tripdraw.common; +package dev.tripdraw.common.log; -import static dev.tripdraw.common.MdcToken.REQUEST_ID; +import static dev.tripdraw.common.log.MdcToken.REQUEST_ID; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; @@ -18,6 +18,5 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha throws IOException, ServletException { MDC.put(REQUEST_ID.key(), UUID.randomUUID().toString()); chain.doFilter(request, response); - } } diff --git a/backend/src/main/java/dev/tripdraw/common/MdcToken.java b/backend/src/main/java/dev/tripdraw/common/log/MdcToken.java similarity index 88% rename from backend/src/main/java/dev/tripdraw/common/MdcToken.java rename to backend/src/main/java/dev/tripdraw/common/log/MdcToken.java index dd400de53..f88ea2416 100644 --- a/backend/src/main/java/dev/tripdraw/common/MdcToken.java +++ b/backend/src/main/java/dev/tripdraw/common/log/MdcToken.java @@ -1,4 +1,4 @@ -package dev.tripdraw.common; +package dev.tripdraw.common.log; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/dev/tripdraw/common/QueryCounter.java b/backend/src/main/java/dev/tripdraw/common/log/QueryCounter.java similarity index 95% rename from backend/src/main/java/dev/tripdraw/common/QueryCounter.java rename to backend/src/main/java/dev/tripdraw/common/log/QueryCounter.java index 633ffc711..2788c9b6b 100644 --- a/backend/src/main/java/dev/tripdraw/common/QueryCounter.java +++ b/backend/src/main/java/dev/tripdraw/common/log/QueryCounter.java @@ -1,4 +1,4 @@ -package dev.tripdraw.common; +package dev.tripdraw.common.log; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/dev/tripdraw/common/QueryInspector.java b/backend/src/main/java/dev/tripdraw/common/log/QueryInspector.java similarity index 95% rename from backend/src/main/java/dev/tripdraw/common/QueryInspector.java rename to backend/src/main/java/dev/tripdraw/common/log/QueryInspector.java index e858c645f..da07a70fa 100644 --- a/backend/src/main/java/dev/tripdraw/common/QueryInspector.java +++ b/backend/src/main/java/dev/tripdraw/common/log/QueryInspector.java @@ -1,4 +1,4 @@ -package dev.tripdraw.common; +package dev.tripdraw.common.log; import java.util.Objects; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/dev/tripdraw/config/swagger/MultipartJackson2HttpMessageConverter.java b/backend/src/main/java/dev/tripdraw/common/swagger/MultipartJackson2HttpMessageConverter.java similarity index 96% rename from backend/src/main/java/dev/tripdraw/config/swagger/MultipartJackson2HttpMessageConverter.java rename to backend/src/main/java/dev/tripdraw/common/swagger/MultipartJackson2HttpMessageConverter.java index 2372aa17e..56868f2c6 100644 --- a/backend/src/main/java/dev/tripdraw/config/swagger/MultipartJackson2HttpMessageConverter.java +++ b/backend/src/main/java/dev/tripdraw/common/swagger/MultipartJackson2HttpMessageConverter.java @@ -1,4 +1,4 @@ -package dev.tripdraw.config.swagger; +package dev.tripdraw.common.swagger; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; diff --git a/backend/src/main/java/dev/tripdraw/config/swagger/SwaggerAuthorizationRequired.java b/backend/src/main/java/dev/tripdraw/common/swagger/SwaggerAuthorizationRequired.java similarity index 81% rename from backend/src/main/java/dev/tripdraw/config/swagger/SwaggerAuthorizationRequired.java rename to backend/src/main/java/dev/tripdraw/common/swagger/SwaggerAuthorizationRequired.java index a63dec77a..e85cf85fd 100644 --- a/backend/src/main/java/dev/tripdraw/config/swagger/SwaggerAuthorizationRequired.java +++ b/backend/src/main/java/dev/tripdraw/common/swagger/SwaggerAuthorizationRequired.java @@ -1,6 +1,6 @@ -package dev.tripdraw.config.swagger; +package dev.tripdraw.common.swagger; -import static dev.tripdraw.config.swagger.SwaggerConfig.BEARER_SECURITY_SCHEME_KEY; +import static dev.tripdraw.common.swagger.SwaggerConfig.BEARER_SECURITY_SCHEME_KEY; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import java.lang.annotation.ElementType; diff --git a/backend/src/main/java/dev/tripdraw/config/swagger/SwaggerConfig.java b/backend/src/main/java/dev/tripdraw/common/swagger/SwaggerConfig.java similarity index 97% rename from backend/src/main/java/dev/tripdraw/config/swagger/SwaggerConfig.java rename to backend/src/main/java/dev/tripdraw/common/swagger/SwaggerConfig.java index fe4d1293a..a2b75c085 100644 --- a/backend/src/main/java/dev/tripdraw/config/swagger/SwaggerConfig.java +++ b/backend/src/main/java/dev/tripdraw/common/swagger/SwaggerConfig.java @@ -1,4 +1,4 @@ -package dev.tripdraw.config.swagger; +package dev.tripdraw.common.swagger; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; diff --git a/backend/src/main/java/dev/tripdraw/dto/validation/NoWhiteSpace.java b/backend/src/main/java/dev/tripdraw/common/validation/NoWhiteSpace.java similarity index 93% rename from backend/src/main/java/dev/tripdraw/dto/validation/NoWhiteSpace.java rename to backend/src/main/java/dev/tripdraw/common/validation/NoWhiteSpace.java index c2a866aed..24d260593 100644 --- a/backend/src/main/java/dev/tripdraw/dto/validation/NoWhiteSpace.java +++ b/backend/src/main/java/dev/tripdraw/common/validation/NoWhiteSpace.java @@ -1,4 +1,4 @@ -package dev.tripdraw.dto.validation; +package dev.tripdraw.common.validation; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/backend/src/main/java/dev/tripdraw/dto/validation/NoWhiteSpaceValidator.java b/backend/src/main/java/dev/tripdraw/common/validation/NoWhiteSpaceValidator.java similarity index 90% rename from backend/src/main/java/dev/tripdraw/dto/validation/NoWhiteSpaceValidator.java rename to backend/src/main/java/dev/tripdraw/common/validation/NoWhiteSpaceValidator.java index f86e4bd9a..1d2a089a9 100644 --- a/backend/src/main/java/dev/tripdraw/dto/validation/NoWhiteSpaceValidator.java +++ b/backend/src/main/java/dev/tripdraw/common/validation/NoWhiteSpaceValidator.java @@ -1,4 +1,4 @@ -package dev.tripdraw.dto.validation; +package dev.tripdraw.common.validation; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/backend/src/main/java/dev/tripdraw/domain/member/MemberRepository.java b/backend/src/main/java/dev/tripdraw/domain/member/MemberRepository.java deleted file mode 100644 index 8d3bcf2a5..000000000 --- a/backend/src/main/java/dev/tripdraw/domain/member/MemberRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.tripdraw.domain.member; - -import dev.tripdraw.domain.oauth.OauthType; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MemberRepository extends JpaRepository { - - Optional findByOauthIdAndOauthType(String oauthId, OauthType oauthType); - - boolean existsByNickname(String nickname); -} diff --git a/backend/src/main/java/dev/tripdraw/domain/post/PostRepository.java b/backend/src/main/java/dev/tripdraw/domain/post/PostRepository.java deleted file mode 100644 index 038e08aa6..000000000 --- a/backend/src/main/java/dev/tripdraw/domain/post/PostRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.tripdraw.domain.post; - -import static dev.tripdraw.exception.post.PostExceptionType.POST_NOT_FOUND; - -import dev.tripdraw.exception.post.PostException; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface PostRepository extends JpaRepository { - - List findAllByTripId(Long tripId); - - default Post getById(Long id) { - return findById(id) - .orElseThrow(() -> new PostException(POST_NOT_FOUND)); - } - - void deleteByMemberId(Long memberId); -} diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/Route.java b/backend/src/main/java/dev/tripdraw/domain/trip/Route.java deleted file mode 100644 index 59bdb48c4..000000000 --- a/backend/src/main/java/dev/tripdraw/domain/trip/Route.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.tripdraw.domain.trip; - -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_IN_TRIP; -import static jakarta.persistence.CascadeType.PERSIST; -import static jakarta.persistence.CascadeType.REMOVE; -import static jakarta.persistence.FetchType.LAZY; -import static lombok.AccessLevel.PROTECTED; - -import dev.tripdraw.exception.trip.TripException; -import jakarta.persistence.Embeddable; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Accessors(fluent = true) -@Getter -@NoArgsConstructor(access = PROTECTED) -@Embeddable -public class Route { - - @OneToMany(fetch = LAZY, cascade = {PERSIST, REMOVE}) - @JoinColumn(name = "trip_id", updatable = false, nullable = false) - private List points = new ArrayList<>(); - - public void add(Point point) { - points.add(point); - } - - public Point findPointById(Long pointIdToFind) { - return points.stream() - .filter(point -> Objects.equals(point.id(), pointIdToFind)) - .findAny() - .orElseThrow(() -> new TripException(POINT_NOT_FOUND)); - } - - public void deletePointById(Long pointId) { - Point pointToDelete = points.stream() - .filter(point -> Objects.equals(point.id(), pointId)) - .findFirst() - .orElseThrow(() -> new TripException(POINT_NOT_IN_TRIP)); - - pointToDelete.delete(); - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/draw/PostCreateEventHandler.java b/backend/src/main/java/dev/tripdraw/draw/application/PostCreateEventHandler.java similarity index 80% rename from backend/src/main/java/dev/tripdraw/application/draw/PostCreateEventHandler.java rename to backend/src/main/java/dev/tripdraw/draw/application/PostCreateEventHandler.java index 1c3ee3c90..9b6f0e324 100644 --- a/backend/src/main/java/dev/tripdraw/application/draw/PostCreateEventHandler.java +++ b/backend/src/main/java/dev/tripdraw/draw/application/PostCreateEventHandler.java @@ -1,17 +1,18 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.post.PostCreateEvent; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import java.util.List; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.domain.PostCreateEvent; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; +import java.util.List; + @Component public class PostCreateEventHandler { @@ -33,7 +34,7 @@ public PostCreateEventHandler( @TransactionalEventListener(phase = AFTER_COMMIT) public void handle(PostCreateEvent postCreateEvent) { Trip trip = tripRepository.getTripWithPoints(postCreateEvent.tripId()); - Post post = postRepository.getById(postCreateEvent.postId()); + Post post = postRepository.getByPostId(postCreateEvent.postId()); String imageUrl = routeImageGenerator.generate( trip.getLatitudes(), diff --git a/backend/src/main/java/dev/tripdraw/application/draw/RouteImageGenerator.java b/backend/src/main/java/dev/tripdraw/draw/application/RouteImageGenerator.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/application/draw/RouteImageGenerator.java rename to backend/src/main/java/dev/tripdraw/draw/application/RouteImageGenerator.java index 1873f2f41..81c4812f6 100644 --- a/backend/src/main/java/dev/tripdraw/application/draw/RouteImageGenerator.java +++ b/backend/src/main/java/dev/tripdraw/draw/application/RouteImageGenerator.java @@ -1,8 +1,8 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; -import dev.tripdraw.domain.draw.Coordinates; -import dev.tripdraw.domain.draw.Positions; -import dev.tripdraw.domain.draw.RouteImageDrawer; +import dev.tripdraw.draw.domain.Coordinates; +import dev.tripdraw.draw.domain.Positions; +import dev.tripdraw.draw.domain.RouteImageDrawer; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/dev/tripdraw/application/draw/RouteImageUploader.java b/backend/src/main/java/dev/tripdraw/draw/application/RouteImageUploader.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/application/draw/RouteImageUploader.java rename to backend/src/main/java/dev/tripdraw/draw/application/RouteImageUploader.java index 57d7ab84a..c2066aaa1 100644 --- a/backend/src/main/java/dev/tripdraw/application/draw/RouteImageUploader.java +++ b/backend/src/main/java/dev/tripdraw/draw/application/RouteImageUploader.java @@ -1,8 +1,8 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; -import static dev.tripdraw.exception.draw.DrawExceptionType.IMAGE_SAVE_FAIL; +import static dev.tripdraw.draw.exception.DrawExceptionType.IMAGE_SAVE_FAIL; -import dev.tripdraw.exception.draw.DrawException; +import dev.tripdraw.draw.exception.DrawException; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; diff --git a/backend/src/main/java/dev/tripdraw/application/draw/TripUpdateEventHandler.java b/backend/src/main/java/dev/tripdraw/draw/application/TripUpdateEventHandler.java similarity index 86% rename from backend/src/main/java/dev/tripdraw/application/draw/TripUpdateEventHandler.java rename to backend/src/main/java/dev/tripdraw/draw/application/TripUpdateEventHandler.java index d24db745f..318e78468 100644 --- a/backend/src/main/java/dev/tripdraw/application/draw/TripUpdateEventHandler.java +++ b/backend/src/main/java/dev/tripdraw/draw/application/TripUpdateEventHandler.java @@ -1,10 +1,10 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.domain.trip.TripUpdateEvent; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.domain.TripUpdateEvent; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; diff --git a/backend/src/main/java/dev/tripdraw/domain/draw/Coordinate.java b/backend/src/main/java/dev/tripdraw/draw/domain/Coordinate.java similarity index 59% rename from backend/src/main/java/dev/tripdraw/domain/draw/Coordinate.java rename to backend/src/main/java/dev/tripdraw/draw/domain/Coordinate.java index 3e6e84156..09407c8b8 100644 --- a/backend/src/main/java/dev/tripdraw/domain/draw/Coordinate.java +++ b/backend/src/main/java/dev/tripdraw/draw/domain/Coordinate.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; public record Coordinate(Double x, Double y) { } diff --git a/backend/src/main/java/dev/tripdraw/domain/draw/Coordinates.java b/backend/src/main/java/dev/tripdraw/draw/domain/Coordinates.java similarity index 95% rename from backend/src/main/java/dev/tripdraw/domain/draw/Coordinates.java rename to backend/src/main/java/dev/tripdraw/draw/domain/Coordinates.java index b71b3c92e..90a52675e 100644 --- a/backend/src/main/java/dev/tripdraw/domain/draw/Coordinates.java +++ b/backend/src/main/java/dev/tripdraw/draw/domain/Coordinates.java @@ -1,10 +1,10 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; -import static dev.tripdraw.exception.draw.DrawExceptionType.INVALID_COORDINATES; +import static dev.tripdraw.draw.exception.DrawExceptionType.INVALID_COORDINATES; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; -import dev.tripdraw.exception.draw.DrawException; +import dev.tripdraw.draw.exception.DrawException; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/backend/src/main/java/dev/tripdraw/domain/draw/Position.java b/backend/src/main/java/dev/tripdraw/draw/domain/Position.java similarity index 59% rename from backend/src/main/java/dev/tripdraw/domain/draw/Position.java rename to backend/src/main/java/dev/tripdraw/draw/domain/Position.java index 0ddb5c173..6a5b346f5 100644 --- a/backend/src/main/java/dev/tripdraw/domain/draw/Position.java +++ b/backend/src/main/java/dev/tripdraw/draw/domain/Position.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; public record Position(Integer x, Integer y) { } diff --git a/backend/src/main/java/dev/tripdraw/domain/draw/Positions.java b/backend/src/main/java/dev/tripdraw/draw/domain/Positions.java similarity index 98% rename from backend/src/main/java/dev/tripdraw/domain/draw/Positions.java rename to backend/src/main/java/dev/tripdraw/draw/domain/Positions.java index 5d5765853..cf30b6f43 100644 --- a/backend/src/main/java/dev/tripdraw/domain/draw/Positions.java +++ b/backend/src/main/java/dev/tripdraw/draw/domain/Positions.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; diff --git a/backend/src/main/java/dev/tripdraw/domain/draw/RouteImageDrawer.java b/backend/src/main/java/dev/tripdraw/draw/domain/RouteImageDrawer.java similarity index 99% rename from backend/src/main/java/dev/tripdraw/domain/draw/RouteImageDrawer.java rename to backend/src/main/java/dev/tripdraw/draw/domain/RouteImageDrawer.java index 687dc50d9..bca7afd6e 100644 --- a/backend/src/main/java/dev/tripdraw/domain/draw/RouteImageDrawer.java +++ b/backend/src/main/java/dev/tripdraw/draw/domain/RouteImageDrawer.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; import static java.awt.BasicStroke.CAP_ROUND; import static java.awt.BasicStroke.JOIN_ROUND; diff --git a/backend/src/main/java/dev/tripdraw/exception/draw/DrawException.java b/backend/src/main/java/dev/tripdraw/draw/exception/DrawException.java similarity index 51% rename from backend/src/main/java/dev/tripdraw/exception/draw/DrawException.java rename to backend/src/main/java/dev/tripdraw/draw/exception/DrawException.java index 1e76b37c5..58230d228 100644 --- a/backend/src/main/java/dev/tripdraw/exception/draw/DrawException.java +++ b/backend/src/main/java/dev/tripdraw/draw/exception/DrawException.java @@ -1,7 +1,7 @@ -package dev.tripdraw.exception.draw; +package dev.tripdraw.draw.exception; -import dev.tripdraw.exception.common.BaseException; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.BaseException; +import dev.tripdraw.common.exception.ExceptionType; public class DrawException extends BaseException { diff --git a/backend/src/main/java/dev/tripdraw/exception/draw/DrawExceptionType.java b/backend/src/main/java/dev/tripdraw/draw/exception/DrawExceptionType.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/exception/draw/DrawExceptionType.java rename to backend/src/main/java/dev/tripdraw/draw/exception/DrawExceptionType.java index bf4fde93a..d5a176bef 100644 --- a/backend/src/main/java/dev/tripdraw/exception/draw/DrawExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/draw/exception/DrawExceptionType.java @@ -1,8 +1,8 @@ -package dev.tripdraw.exception.draw; +package dev.tripdraw.draw.exception; import static org.springframework.http.HttpStatus.BAD_REQUEST; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.ExceptionType; import org.springframework.http.HttpStatus; public enum DrawExceptionType implements ExceptionType { diff --git a/backend/src/main/java/dev/tripdraw/dto/auth/OauthInfo.java b/backend/src/main/java/dev/tripdraw/dto/auth/OauthInfo.java deleted file mode 100644 index e1b35720c..000000000 --- a/backend/src/main/java/dev/tripdraw/dto/auth/OauthInfo.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.tripdraw.dto.auth; - -import dev.tripdraw.domain.oauth.OauthType; - -public record OauthInfo(String oauthId, OauthType oauthType) { -} diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/TripsSearchResponse.java b/backend/src/main/java/dev/tripdraw/dto/trip/TripsSearchResponse.java deleted file mode 100644 index 4d17bf4bc..000000000 --- a/backend/src/main/java/dev/tripdraw/dto/trip/TripsSearchResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.tripdraw.dto.trip; - -import static java.util.stream.Collectors.collectingAndThen; - -import dev.tripdraw.domain.trip.Trip; -import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; -import java.util.stream.Collectors; - -public record TripsSearchResponse( - @Schema(description = "여행 목록") - List trips -) { - - public static TripsSearchResponse from(List trips) { - return trips.stream() - .map(TripSearchResponse::from) - .collect(collectingAndThen(Collectors.toList(), TripsSearchResponse::new)); - } -} diff --git a/backend/src/main/java/dev/tripdraw/application/file/FilePath.java b/backend/src/main/java/dev/tripdraw/file/application/FilePath.java similarity index 75% rename from backend/src/main/java/dev/tripdraw/application/file/FilePath.java rename to backend/src/main/java/dev/tripdraw/file/application/FilePath.java index 23de87062..a87e1b91d 100644 --- a/backend/src/main/java/dev/tripdraw/application/file/FilePath.java +++ b/backend/src/main/java/dev/tripdraw/file/application/FilePath.java @@ -1,15 +1,15 @@ -package dev.tripdraw.application.file; +package dev.tripdraw.file.application; -import static dev.tripdraw.domain.file.FileType.POST_IMAGE; +import static dev.tripdraw.file.domain.FileType.IMAGE; -import dev.tripdraw.domain.file.FileType; +import dev.tripdraw.file.domain.FileType; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class FilePath { - + private final String base; private final String postImagePath; @@ -20,7 +20,7 @@ public FilePath(@Value("${trip.base}") String base, @Value("${trip.post}") Strin public String getPath(FileType fileType) { Map typeAndPath = Map.of( - POST_IMAGE, postImagePath + IMAGE, postImagePath ); return typeAndPath.get(fileType); @@ -28,7 +28,7 @@ public String getPath(FileType fileType) { public String getPathWithBase(FileType fileType) { Map typeAndPath = Map.of( - POST_IMAGE, base + postImagePath + IMAGE, base + postImagePath ); return typeAndPath.get(fileType); diff --git a/backend/src/main/java/dev/tripdraw/application/file/FileUploader.java b/backend/src/main/java/dev/tripdraw/file/application/FileUploader.java similarity index 62% rename from backend/src/main/java/dev/tripdraw/application/file/FileUploader.java rename to backend/src/main/java/dev/tripdraw/file/application/FileUploader.java index df7360e8a..b4935282e 100644 --- a/backend/src/main/java/dev/tripdraw/application/file/FileUploader.java +++ b/backend/src/main/java/dev/tripdraw/file/application/FileUploader.java @@ -1,9 +1,9 @@ -package dev.tripdraw.application.file; +package dev.tripdraw.file.application; -import static dev.tripdraw.exception.file.FileIOExceptionType.FILE_SAVE_FAIL; +import static dev.tripdraw.file.exception.FileIOExceptionType.FILE_SAVE_FAIL; -import dev.tripdraw.domain.file.FileType; -import dev.tripdraw.exception.file.FileIOException; +import dev.tripdraw.file.domain.FileType; +import dev.tripdraw.file.exception.FileIOException; import java.io.File; import java.io.IOException; import java.util.UUID; @@ -18,16 +18,16 @@ public class FileUploader { private final FilePath filePath; private final FileUrlMaker fileUrlMaker; - public String upload(MultipartFile file, FileType fileType) { + public String upload(MultipartFile file) { if (file == null || file.isEmpty()) { return null; } - + FileType type = FileType.from(file.getContentType()); UUID id = UUID.randomUUID(); - String filePathWithBase = filePath.getPathWithBase(fileType) + id + fileType.extension(); + String filePathWithBase = filePath.getPathWithBase(type) + id + type.extension(); fileUpload(file, filePathWithBase); - return fileUrlMaker.make(filePath.getPath(fileType) + id + fileType.extension()); + return fileUrlMaker.make(filePath.getPath(type) + id + type.extension()); } private void fileUpload(MultipartFile file, String filePathWithBase) { diff --git a/backend/src/main/java/dev/tripdraw/application/file/FileUrlMaker.java b/backend/src/main/java/dev/tripdraw/file/application/FileUrlMaker.java similarity index 90% rename from backend/src/main/java/dev/tripdraw/application/file/FileUrlMaker.java rename to backend/src/main/java/dev/tripdraw/file/application/FileUrlMaker.java index fd66c9286..3a40be4da 100644 --- a/backend/src/main/java/dev/tripdraw/application/file/FileUrlMaker.java +++ b/backend/src/main/java/dev/tripdraw/file/application/FileUrlMaker.java @@ -1,4 +1,4 @@ -package dev.tripdraw.application.file; +package dev.tripdraw.file.application; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/dev/tripdraw/domain/file/FileType.java b/backend/src/main/java/dev/tripdraw/file/domain/FileType.java similarity index 80% rename from backend/src/main/java/dev/tripdraw/domain/file/FileType.java rename to backend/src/main/java/dev/tripdraw/file/domain/FileType.java index 8188d8549..9a627bd4d 100644 --- a/backend/src/main/java/dev/tripdraw/domain/file/FileType.java +++ b/backend/src/main/java/dev/tripdraw/file/domain/FileType.java @@ -1,9 +1,9 @@ -package dev.tripdraw.domain.file; +package dev.tripdraw.file.domain; -import static dev.tripdraw.exception.file.FileIOExceptionType.INVALID_CONTENT_TYPE; +import static dev.tripdraw.file.exception.FileIOExceptionType.INVALID_CONTENT_TYPE; import static org.springframework.http.MediaType.IMAGE_JPEG_VALUE; -import dev.tripdraw.exception.file.FileIOException; +import dev.tripdraw.file.exception.FileIOException; import java.util.Arrays; import java.util.Objects; import lombok.Getter; @@ -12,7 +12,7 @@ @Accessors(fluent = true) @Getter public enum FileType { - POST_IMAGE(IMAGE_JPEG_VALUE, ".jpg"), + IMAGE(IMAGE_JPEG_VALUE, ".jpg"), ; private final String contentType; diff --git a/backend/src/main/java/dev/tripdraw/exception/file/FileIOException.java b/backend/src/main/java/dev/tripdraw/file/exception/FileIOException.java similarity index 51% rename from backend/src/main/java/dev/tripdraw/exception/file/FileIOException.java rename to backend/src/main/java/dev/tripdraw/file/exception/FileIOException.java index 6a417302b..f86004141 100644 --- a/backend/src/main/java/dev/tripdraw/exception/file/FileIOException.java +++ b/backend/src/main/java/dev/tripdraw/file/exception/FileIOException.java @@ -1,7 +1,7 @@ -package dev.tripdraw.exception.file; +package dev.tripdraw.file.exception; -import dev.tripdraw.exception.common.BaseException; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.BaseException; +import dev.tripdraw.common.exception.ExceptionType; public class FileIOException extends BaseException { diff --git a/backend/src/main/java/dev/tripdraw/exception/file/FileIOExceptionType.java b/backend/src/main/java/dev/tripdraw/file/exception/FileIOExceptionType.java similarity index 90% rename from backend/src/main/java/dev/tripdraw/exception/file/FileIOExceptionType.java rename to backend/src/main/java/dev/tripdraw/file/exception/FileIOExceptionType.java index 186c74b63..d54f97fe3 100644 --- a/backend/src/main/java/dev/tripdraw/exception/file/FileIOExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/file/exception/FileIOExceptionType.java @@ -1,9 +1,9 @@ -package dev.tripdraw.exception.file; +package dev.tripdraw.file.exception; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.ExceptionType; import org.springframework.http.HttpStatus; public enum FileIOExceptionType implements ExceptionType { diff --git a/backend/src/main/java/dev/tripdraw/member/application/MemberService.java b/backend/src/main/java/dev/tripdraw/member/application/MemberService.java new file mode 100644 index 000000000..eb59ec703 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/member/application/MemberService.java @@ -0,0 +1,37 @@ +package dev.tripdraw.member.application; + +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberDeleteEvent; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.dto.MemberSearchResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class MemberService { + + private final MemberRepository memberRepository; + private final ApplicationEventPublisher publisher; + + @Transactional(readOnly = true) + public boolean existsById(Long memberId) { + return memberRepository.existsById(memberId); + } + + @Transactional(readOnly = true) + public MemberSearchResponse find(LoginUser loginUser) { + Member member = memberRepository.getById(loginUser.memberId()); + return MemberSearchResponse.from(member); + } + + public void delete(LoginUser loginUser) { + Long memberId = loginUser.memberId(); + publisher.publishEvent(new MemberDeleteEvent(memberId)); + memberRepository.deleteById(memberId); + } +} diff --git a/backend/src/main/java/dev/tripdraw/domain/member/Member.java b/backend/src/main/java/dev/tripdraw/member/domain/Member.java similarity index 91% rename from backend/src/main/java/dev/tripdraw/domain/member/Member.java rename to backend/src/main/java/dev/tripdraw/member/domain/Member.java index f68766575..bdfb4251e 100644 --- a/backend/src/main/java/dev/tripdraw/domain/member/Member.java +++ b/backend/src/main/java/dev/tripdraw/member/domain/Member.java @@ -1,10 +1,10 @@ -package dev.tripdraw.domain.member; +package dev.tripdraw.member.domain; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import dev.tripdraw.domain.common.BaseEntity; -import dev.tripdraw.domain.oauth.OauthType; +import dev.tripdraw.common.auth.OauthType; +import dev.tripdraw.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/backend/src/main/java/dev/tripdraw/member/domain/MemberDeleteEvent.java b/backend/src/main/java/dev/tripdraw/member/domain/MemberDeleteEvent.java new file mode 100644 index 000000000..8fe1df59b --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/member/domain/MemberDeleteEvent.java @@ -0,0 +1,4 @@ +package dev.tripdraw.member.domain; + +public record MemberDeleteEvent(Long memberId) { +} diff --git a/backend/src/main/java/dev/tripdraw/member/domain/MemberRepository.java b/backend/src/main/java/dev/tripdraw/member/domain/MemberRepository.java new file mode 100644 index 000000000..7828bd7c1 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/member/domain/MemberRepository.java @@ -0,0 +1,20 @@ +package dev.tripdraw.member.domain; + +import static dev.tripdraw.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; + +import dev.tripdraw.common.auth.OauthType; +import dev.tripdraw.member.exception.MemberException; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findByOauthIdAndOauthType(String oauthId, OauthType oauthType); + + boolean existsByNickname(String nickname); + + default Member getById(Long id) { + return findById(id) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + } +} diff --git a/backend/src/main/java/dev/tripdraw/dto/member/MemberSearchResponse.java b/backend/src/main/java/dev/tripdraw/member/dto/MemberSearchResponse.java similarity index 87% rename from backend/src/main/java/dev/tripdraw/dto/member/MemberSearchResponse.java rename to backend/src/main/java/dev/tripdraw/member/dto/MemberSearchResponse.java index e84b5929d..7d6e4b949 100644 --- a/backend/src/main/java/dev/tripdraw/dto/member/MemberSearchResponse.java +++ b/backend/src/main/java/dev/tripdraw/member/dto/MemberSearchResponse.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.member; +package dev.tripdraw.member.dto; -import dev.tripdraw.domain.member.Member; +import dev.tripdraw.member.domain.Member; import io.swagger.v3.oas.annotations.media.Schema; import java.util.Objects; diff --git a/backend/src/main/java/dev/tripdraw/exception/member/MemberException.java b/backend/src/main/java/dev/tripdraw/member/exception/MemberException.java similarity index 51% rename from backend/src/main/java/dev/tripdraw/exception/member/MemberException.java rename to backend/src/main/java/dev/tripdraw/member/exception/MemberException.java index cd4ccb936..89c47d17e 100644 --- a/backend/src/main/java/dev/tripdraw/exception/member/MemberException.java +++ b/backend/src/main/java/dev/tripdraw/member/exception/MemberException.java @@ -1,7 +1,7 @@ -package dev.tripdraw.exception.member; +package dev.tripdraw.member.exception; -import dev.tripdraw.exception.common.BaseException; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.BaseException; +import dev.tripdraw.common.exception.ExceptionType; public class MemberException extends BaseException { diff --git a/backend/src/main/java/dev/tripdraw/exception/member/MemberExceptionType.java b/backend/src/main/java/dev/tripdraw/member/exception/MemberExceptionType.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/exception/member/MemberExceptionType.java rename to backend/src/main/java/dev/tripdraw/member/exception/MemberExceptionType.java index 645c593d3..e6a7b31f8 100644 --- a/backend/src/main/java/dev/tripdraw/exception/member/MemberExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/member/exception/MemberExceptionType.java @@ -1,9 +1,9 @@ -package dev.tripdraw.exception.member; +package dev.tripdraw.member.exception; import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.NOT_FOUND; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.ExceptionType; import org.springframework.http.HttpStatus; public enum MemberExceptionType implements ExceptionType { diff --git a/backend/src/main/java/dev/tripdraw/presentation/controller/MemberController.java b/backend/src/main/java/dev/tripdraw/member/presentation/MemberController.java similarity index 66% rename from backend/src/main/java/dev/tripdraw/presentation/controller/MemberController.java rename to backend/src/main/java/dev/tripdraw/member/presentation/MemberController.java index 1720a1229..843c6436d 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/controller/MemberController.java +++ b/backend/src/main/java/dev/tripdraw/member/presentation/MemberController.java @@ -1,7 +1,10 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.member.presentation; -import dev.tripdraw.application.MemberService; -import dev.tripdraw.dto.member.MemberSearchResponse; +import dev.tripdraw.common.auth.Auth; +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.common.swagger.SwaggerAuthorizationRequired; +import dev.tripdraw.member.application.MemberService; +import dev.tripdraw.member.dto.MemberSearchResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -10,11 +13,11 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Tag(name = "Member", description = "사용자 관련 API 명세") @RequiredArgsConstructor +@SwaggerAuthorizationRequired @RequestMapping("/members") @RestController public class MemberController { @@ -26,9 +29,9 @@ public class MemberController { responseCode = "200", description = "사용자 조회 성공." ) - @GetMapping - public ResponseEntity findByCode(@RequestParam String code) { - MemberSearchResponse response = memberService.findByCode(code); + @GetMapping("/me") + public ResponseEntity find(@Auth LoginUser loginUser) { + MemberSearchResponse response = memberService.find(loginUser); return ResponseEntity.ok(response); } @@ -37,9 +40,9 @@ public ResponseEntity findByCode(@RequestParam String code responseCode = "204", description = "사용자 삭제 성공." ) - @DeleteMapping - public ResponseEntity delete(@RequestParam String code) { - memberService.deleteByCode(code); + @DeleteMapping("/me") + public ResponseEntity delete(@Auth LoginUser loginUser) { + memberService.delete(loginUser); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/dev/tripdraw/post/application/PostDeleteEventHandler.java b/backend/src/main/java/dev/tripdraw/post/application/PostDeleteEventHandler.java new file mode 100644 index 000000000..8be36b7a0 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/application/PostDeleteEventHandler.java @@ -0,0 +1,25 @@ +package dev.tripdraw.post.application; + +import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE; + +import dev.tripdraw.member.domain.MemberDeleteEvent; +import dev.tripdraw.post.domain.PostRepository; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Component +public class PostDeleteEventHandler { + + private final PostRepository postRepository; + + public PostDeleteEventHandler(PostRepository postRepository) { + this.postRepository = postRepository; + } + + @Order(HIGHEST_PRECEDENCE) + @EventListener + public void deletePostByMemberId(MemberDeleteEvent event) { + postRepository.deleteByMemberId(event.memberId()); + } +} diff --git a/backend/src/main/java/dev/tripdraw/post/application/PostQueryService.java b/backend/src/main/java/dev/tripdraw/post/application/PostQueryService.java new file mode 100644 index 000000000..38d59e772 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/application/PostQueryService.java @@ -0,0 +1,25 @@ +package dev.tripdraw.post.application; + +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.dto.PostSearchConditions; +import dev.tripdraw.post.dto.PostSearchPaging; +import dev.tripdraw.post.query.PostCustomRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class PostQueryService { + + private final PostCustomRepository postCustomRepository; + + public PostQueryService(PostCustomRepository postCustomRepository) { + this.postCustomRepository = postCustomRepository; + } + + @Transactional(readOnly = true) + public List findAllByConditions(PostSearchConditions conditions, PostSearchPaging paging) { + return postCustomRepository.findAllByConditions(conditions, paging); + } +} diff --git a/backend/src/main/java/dev/tripdraw/post/application/PostService.java b/backend/src/main/java/dev/tripdraw/post/application/PostService.java new file mode 100644 index 000000000..08557ab54 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/application/PostService.java @@ -0,0 +1,153 @@ +package dev.tripdraw.post.application; + +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_NOT_FOUND; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.file.application.FileUploader; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.domain.PostCreateEvent; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.post.dto.PostAndPointCreateRequest; +import dev.tripdraw.post.dto.PostCreateResponse; +import dev.tripdraw.post.dto.PostRequest; +import dev.tripdraw.post.dto.PostResponse; +import dev.tripdraw.post.dto.PostSearchPaging; +import dev.tripdraw.post.dto.PostSearchRequest; +import dev.tripdraw.post.dto.PostSearchResponse; +import dev.tripdraw.post.dto.PostUpdateRequest; +import dev.tripdraw.post.dto.PostsResponse; +import dev.tripdraw.post.dto.PostsSearchResponse; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.PointRepository; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.exception.TripException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional +@Service +public class PostService { + + private final PostQueryService postQueryService; + private final PostRepository postRepository; + private final TripRepository tripRepository; + private final PointRepository pointRepository; + private final MemberRepository memberRepository; + private final FileUploader fileUploader; + private final ApplicationEventPublisher applicationEventPublisher; + + public PostCreateResponse addAtCurrentPoint( + LoginUser loginUser, + PostAndPointCreateRequest postAndPointCreateRequest, + MultipartFile file + ) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(postAndPointCreateRequest.tripId()); + trip.validateAuthorization(member); + Point point = pointRepository.save(postAndPointCreateRequest.toPoint(trip)); + + Post post = postAndPointCreateRequest.toPost(member, point); + uploadImage(file, post, trip); + Post savedPost = postRepository.save(post); + + applicationEventPublisher.publishEvent(new PostCreateEvent(post.id(), trip.id())); + return PostCreateResponse.from(savedPost); + } + + private void uploadImage(MultipartFile file, Post post, Trip trip) { + String filename = fileUploader.upload(file); + if (filename == null) { + return; + } + post.changePostImageUrl(filename); + trip.changeImageUrl(filename); + } + + public PostCreateResponse addAtExistingLocation( + LoginUser loginUser, + PostRequest postRequest, + MultipartFile file + ) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(postRequest.tripId()); + trip.validateAuthorization(member); + Point point = pointRepository.getById(postRequest.pointId()); + + Post post = postRequest.toPost(member, point); + uploadImage(file, post, trip); + Post savedPost = postRepository.save(post); + + applicationEventPublisher.publishEvent(new PostCreateEvent(post.id(), trip.id())); + return PostCreateResponse.from(savedPost); + } + + @Transactional(readOnly = true) + public PostResponse read(Long postId) { + Post post = postRepository.getByPostId(postId); + return PostResponse.from(post); + } + + @Transactional(readOnly = true) + public PostsResponse readAllByTripId(Long tripId) { + if (!tripRepository.existsById(tripId)) { + throw new TripException(TRIP_NOT_FOUND); + } + + return postRepository.findAllByTripId(tripId).stream() + .sorted(comparing(Post::pointRecordedAt).reversed()) + .collect(collectingAndThen(toList(), PostsResponse::from)); + } + + public void update(LoginUser loginUser, Long postId, PostUpdateRequest postUpdateRequest, MultipartFile file) { + Post post = postRepository.getByPostId(postId); + Member member = memberRepository.getById(loginUser.memberId()); + post.validateAuthorization(member); + Trip trip = tripRepository.getById(post.tripId()); + trip.validateAuthorization(member); + + post.changeTitle(postUpdateRequest.title()); + post.changeWriting(postUpdateRequest.writing()); + uploadImage(file, post, trip); + } + + public void delete(LoginUser loginUser, Long postId) { + Post post = postRepository.getByPostId(postId); + Member member = memberRepository.getById(loginUser.memberId()); + post.validateAuthorization(member); + postRepository.deleteById(postId); + } + + @Transactional(readOnly = true) + public PostsSearchResponse readAll(PostSearchRequest postSearchRequest) { + PostSearchPaging postSearchPaging = postSearchRequest.toPostSearchPaging(); + + List posts = postQueryService.findAllByConditions( + postSearchRequest.toPostSearchConditions(), + postSearchPaging + ); + + List postSearchResponses = posts.stream() + .map(PostSearchResponse::from) + .toList(); + boolean hasNextPage = (posts.size() == postSearchPaging.limit() + 1); + + if (hasNextPage) { + postSearchResponses = postSearchResponses.subList(0, postSearchPaging.limit()); + } + + return PostsSearchResponse.of(postSearchResponses, hasNextPage); + } +} + diff --git a/backend/src/main/java/dev/tripdraw/domain/post/Post.java b/backend/src/main/java/dev/tripdraw/post/domain/Post.java similarity index 90% rename from backend/src/main/java/dev/tripdraw/domain/post/Post.java rename to backend/src/main/java/dev/tripdraw/post/domain/Post.java index bcafa9cdc..da6cead66 100644 --- a/backend/src/main/java/dev/tripdraw/domain/post/Post.java +++ b/backend/src/main/java/dev/tripdraw/post/domain/Post.java @@ -1,14 +1,14 @@ -package dev.tripdraw.domain.post; +package dev.tripdraw.post.domain; -import static dev.tripdraw.exception.post.PostExceptionType.NOT_AUTHORIZED_TO_POST; +import static dev.tripdraw.post.exception.PostExceptionType.NOT_AUTHORIZED_TO_POST; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import dev.tripdraw.domain.common.BaseEntity; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.exception.post.PostException; +import dev.tripdraw.common.entity.BaseEntity; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.post.exception.PostException; +import dev.tripdraw.trip.domain.Point; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/backend/src/main/java/dev/tripdraw/domain/post/PostCreateEvent.java b/backend/src/main/java/dev/tripdraw/post/domain/PostCreateEvent.java similarity index 64% rename from backend/src/main/java/dev/tripdraw/domain/post/PostCreateEvent.java rename to backend/src/main/java/dev/tripdraw/post/domain/PostCreateEvent.java index 3f3d777d8..1f90b1c39 100644 --- a/backend/src/main/java/dev/tripdraw/domain/post/PostCreateEvent.java +++ b/backend/src/main/java/dev/tripdraw/post/domain/PostCreateEvent.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.post; +package dev.tripdraw.post.domain; public record PostCreateEvent(Long postId, Long tripId) { } diff --git a/backend/src/main/java/dev/tripdraw/post/domain/PostRepository.java b/backend/src/main/java/dev/tripdraw/post/domain/PostRepository.java new file mode 100644 index 000000000..7baa16bd7 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/domain/PostRepository.java @@ -0,0 +1,23 @@ +package dev.tripdraw.post.domain; + +import static dev.tripdraw.post.exception.PostExceptionType.POST_NOT_FOUND; + +import dev.tripdraw.post.exception.PostException; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface PostRepository extends JpaRepository { + + @Query("SELECT p FROM Post p JOIN FETCH p.point where p.tripId = :tripId") + List findAllByTripId(@Param("tripId") Long tripId); + + default Post getByPostId(Long id) { + return findById(id) + .orElseThrow(() -> new PostException(POST_NOT_FOUND)); + } + + void deleteByMemberId(Long memberId); +} diff --git a/backend/src/main/java/dev/tripdraw/dto/post/PostAndPointCreateRequest.java b/backend/src/main/java/dev/tripdraw/post/dto/PostAndPointCreateRequest.java similarity index 85% rename from backend/src/main/java/dev/tripdraw/dto/post/PostAndPointCreateRequest.java rename to backend/src/main/java/dev/tripdraw/post/dto/PostAndPointCreateRequest.java index 056bd77ff..1f368b674 100644 --- a/backend/src/main/java/dev/tripdraw/dto/post/PostAndPointCreateRequest.java +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostAndPointCreateRequest.java @@ -1,11 +1,12 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; import static com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING; import com.fasterxml.jackson.annotation.JsonFormat; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.trip.Point; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -48,6 +49,10 @@ public Point toPoint() { return new Point(latitude, longitude, recordedAt); } + public Point toPoint(Trip trip) { + return new Point(latitude, longitude, recordedAt, trip); + } + public Post toPost(Member member, Point point) { return new Post(title, point, address, writing, member, tripId); } diff --git a/backend/src/main/java/dev/tripdraw/dto/post/PostCreateResponse.java b/backend/src/main/java/dev/tripdraw/post/dto/PostCreateResponse.java similarity index 80% rename from backend/src/main/java/dev/tripdraw/dto/post/PostCreateResponse.java rename to backend/src/main/java/dev/tripdraw/post/dto/PostCreateResponse.java index d971d6bfe..22ee61dc9 100644 --- a/backend/src/main/java/dev/tripdraw/dto/post/PostCreateResponse.java +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostCreateResponse.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; -import dev.tripdraw.domain.post.Post; +import dev.tripdraw.post.domain.Post; import io.swagger.v3.oas.annotations.media.Schema; public record PostCreateResponse( diff --git a/backend/src/main/java/dev/tripdraw/dto/post/PostRequest.java b/backend/src/main/java/dev/tripdraw/post/dto/PostRequest.java similarity index 87% rename from backend/src/main/java/dev/tripdraw/dto/post/PostRequest.java rename to backend/src/main/java/dev/tripdraw/post/dto/PostRequest.java index 2a7fd4fd0..e09e8a430 100644 --- a/backend/src/main/java/dev/tripdraw/dto/post/PostRequest.java +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostRequest.java @@ -1,8 +1,8 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.trip.Point; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.trip.domain.Point; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/backend/src/main/java/dev/tripdraw/dto/post/PostResponse.java b/backend/src/main/java/dev/tripdraw/post/dto/PostResponse.java similarity index 79% rename from backend/src/main/java/dev/tripdraw/dto/post/PostResponse.java rename to backend/src/main/java/dev/tripdraw/post/dto/PostResponse.java index ebdd836be..3dcfdb93f 100644 --- a/backend/src/main/java/dev/tripdraw/dto/post/PostResponse.java +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostResponse.java @@ -1,8 +1,9 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.dto.trip.PointResponse; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.trip.dto.PointResponse; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.Objects; public record PostResponse( @Schema(description = "감상의 Id", example = "1") @@ -30,6 +31,8 @@ public record PostResponse( String routeImageUrl ) { + private static final String EMPTY_IMAGE_URL = ""; + public static PostResponse from(Post post) { return new PostResponse( post.id(), @@ -38,8 +41,8 @@ public static PostResponse from(Post post) { post.address(), post.writing(), PointResponse.from(post.point()), - post.postImageUrl(), - post.routeImageUrl() + Objects.requireNonNullElse(post.postImageUrl(), EMPTY_IMAGE_URL), + Objects.requireNonNullElse(post.routeImageUrl(), EMPTY_IMAGE_URL) ); } } diff --git a/backend/src/main/java/dev/tripdraw/post/dto/PostSearchConditions.java b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchConditions.java new file mode 100644 index 000000000..b9917e369 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchConditions.java @@ -0,0 +1,17 @@ +package dev.tripdraw.post.dto; + +import lombok.Builder; + +import java.util.Set; + +@Builder +public record PostSearchConditions( + Set years, + Set months, + Set daysOfWeek, + Set hours, + Set ageRanges, + Set genders, + String address +) { +} diff --git a/backend/src/main/java/dev/tripdraw/post/dto/PostSearchPaging.java b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchPaging.java new file mode 100644 index 000000000..80c1ec661 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchPaging.java @@ -0,0 +1,4 @@ +package dev.tripdraw.post.dto; + +public record PostSearchPaging(Long lastViewedId, Integer limit) { +} diff --git a/backend/src/main/java/dev/tripdraw/post/dto/PostSearchRequest.java b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchRequest.java new file mode 100644 index 000000000..1426fba3d --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchRequest.java @@ -0,0 +1,34 @@ +package dev.tripdraw.post.dto; + +import java.util.Set; +import lombok.Builder; + +@Builder +public record PostSearchRequest( + Set years, + Set months, + Set daysOfWeek, + Set hours, + Set ageRanges, + Set genders, + String address, + Long lastViewedId, + Integer limit +) { + + public PostSearchConditions toPostSearchConditions() { + return PostSearchConditions.builder() + .years(years) + .months(months) + .daysOfWeek(daysOfWeek) + .hours(hours) + .ageRanges(ageRanges) + .genders(genders) + .address(address) + .build(); + } + + public PostSearchPaging toPostSearchPaging() { + return new PostSearchPaging(lastViewedId, limit); + } +} diff --git a/backend/src/main/java/dev/tripdraw/post/dto/PostSearchResponse.java b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchResponse.java new file mode 100644 index 000000000..e4ba1a3a7 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostSearchResponse.java @@ -0,0 +1,32 @@ +package dev.tripdraw.post.dto; + +import dev.tripdraw.post.domain.Post; +import java.time.LocalDateTime; +import java.util.Objects; + +public record PostSearchResponse( + Long postId, + Long tripId, + String title, + String address, + String writing, + String postImageUrl, + String routeImageUrl, + LocalDateTime recordedAt +) { + + private static final String EMPTY_IMAGE_URL = ""; + + public static PostSearchResponse from(Post post) { + return new PostSearchResponse( + post.id(), + post.tripId(), + post.title(), + post.address(), + post.writing(), + Objects.requireNonNullElse(post.postImageUrl(), EMPTY_IMAGE_URL), + Objects.requireNonNullElse(post.routeImageUrl(), EMPTY_IMAGE_URL), + post.pointRecordedAt() + ); + } +} diff --git a/backend/src/main/java/dev/tripdraw/dto/post/PostUpdateRequest.java b/backend/src/main/java/dev/tripdraw/post/dto/PostUpdateRequest.java similarity index 94% rename from backend/src/main/java/dev/tripdraw/dto/post/PostUpdateRequest.java rename to backend/src/main/java/dev/tripdraw/post/dto/PostUpdateRequest.java index bacbf4ad9..9eb63b08c 100644 --- a/backend/src/main/java/dev/tripdraw/dto/post/PostUpdateRequest.java +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostUpdateRequest.java @@ -1,4 +1,4 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/backend/src/main/java/dev/tripdraw/dto/post/PostsResponse.java b/backend/src/main/java/dev/tripdraw/post/dto/PostsResponse.java similarity index 84% rename from backend/src/main/java/dev/tripdraw/dto/post/PostsResponse.java rename to backend/src/main/java/dev/tripdraw/post/dto/PostsResponse.java index 5fec95c23..02d9d255c 100644 --- a/backend/src/main/java/dev/tripdraw/dto/post/PostsResponse.java +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostsResponse.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; -import dev.tripdraw.domain.post.Post; +import dev.tripdraw.post.domain.Post; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; diff --git a/backend/src/main/java/dev/tripdraw/post/dto/PostsSearchResponse.java b/backend/src/main/java/dev/tripdraw/post/dto/PostsSearchResponse.java new file mode 100644 index 000000000..7e713d7d0 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/dto/PostsSearchResponse.java @@ -0,0 +1,9 @@ +package dev.tripdraw.post.dto; + +import java.util.List; + +public record PostsSearchResponse(List posts, boolean hasNextPage) { + public static PostsSearchResponse of(List postSearchResponses, boolean hasNextPage) { + return new PostsSearchResponse(postSearchResponses, hasNextPage); + } +} diff --git a/backend/src/main/java/dev/tripdraw/exception/post/PostException.java b/backend/src/main/java/dev/tripdraw/post/exception/PostException.java similarity index 51% rename from backend/src/main/java/dev/tripdraw/exception/post/PostException.java rename to backend/src/main/java/dev/tripdraw/post/exception/PostException.java index bf32472c8..e8a3eceb2 100644 --- a/backend/src/main/java/dev/tripdraw/exception/post/PostException.java +++ b/backend/src/main/java/dev/tripdraw/post/exception/PostException.java @@ -1,7 +1,7 @@ -package dev.tripdraw.exception.post; +package dev.tripdraw.post.exception; -import dev.tripdraw.exception.common.BaseException; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.BaseException; +import dev.tripdraw.common.exception.ExceptionType; public class PostException extends BaseException { diff --git a/backend/src/main/java/dev/tripdraw/exception/post/PostExceptionType.java b/backend/src/main/java/dev/tripdraw/post/exception/PostExceptionType.java similarity index 89% rename from backend/src/main/java/dev/tripdraw/exception/post/PostExceptionType.java rename to backend/src/main/java/dev/tripdraw/post/exception/PostExceptionType.java index 98e274864..036c00137 100644 --- a/backend/src/main/java/dev/tripdraw/exception/post/PostExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/post/exception/PostExceptionType.java @@ -1,9 +1,9 @@ -package dev.tripdraw.exception.post; +package dev.tripdraw.post.exception; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.ExceptionType; import org.springframework.http.HttpStatus; public enum PostExceptionType implements ExceptionType { diff --git a/backend/src/main/java/dev/tripdraw/presentation/controller/PostController.java b/backend/src/main/java/dev/tripdraw/post/presentation/PostController.java similarity index 82% rename from backend/src/main/java/dev/tripdraw/presentation/controller/PostController.java rename to backend/src/main/java/dev/tripdraw/post/presentation/PostController.java index e663859b7..263ae2c31 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/controller/PostController.java +++ b/backend/src/main/java/dev/tripdraw/post/presentation/PostController.java @@ -1,20 +1,22 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.post.presentation; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; -import dev.tripdraw.application.PostService; -import dev.tripdraw.config.swagger.SwaggerAuthorizationRequired; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.dto.post.PostAndPointCreateRequest; -import dev.tripdraw.dto.post.PostCreateResponse; -import dev.tripdraw.dto.post.PostRequest; -import dev.tripdraw.dto.post.PostResponse; -import dev.tripdraw.dto.post.PostUpdateRequest; -import dev.tripdraw.dto.post.PostsResponse; -import dev.tripdraw.presentation.member.Auth; +import dev.tripdraw.common.auth.Auth; +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.common.swagger.SwaggerAuthorizationRequired; +import dev.tripdraw.post.application.PostService; +import dev.tripdraw.post.dto.PostAndPointCreateRequest; +import dev.tripdraw.post.dto.PostCreateResponse; +import dev.tripdraw.post.dto.PostRequest; +import dev.tripdraw.post.dto.PostResponse; +import dev.tripdraw.post.dto.PostSearchRequest; +import dev.tripdraw.post.dto.PostUpdateRequest; +import dev.tripdraw.post.dto.PostsResponse; +import dev.tripdraw.post.dto.PostsSearchResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -103,7 +105,7 @@ public ResponseEntity read( @Auth LoginUser loginUser, @PathVariable Long postId ) { - PostResponse response = postService.read(loginUser, postId); + PostResponse response = postService.read(postId); return ResponseEntity.ok(response); } @@ -117,7 +119,21 @@ public ResponseEntity readAllPostsOfTrip( @Auth LoginUser loginUser, @PathVariable Long tripId ) { - PostsResponse response = postService.readAllByTripId(loginUser, tripId); + PostsResponse response = postService.readAllByTripId(tripId); + return ResponseEntity.ok(response); + } + + @Operation(summary = "모든 감상 조회 API", description = "모든 감상을 조회합니다.") + @ApiResponse( + responseCode = "200", + description = "모든 감상 조회 성공." + ) + @GetMapping("/posts") + public ResponseEntity readAllPosts( + @Auth LoginUser loginUser, + PostSearchRequest postSearchRequest + ) { + PostsSearchResponse response = postService.readAll(postSearchRequest); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/dev/tripdraw/post/query/PostCustomRepository.java b/backend/src/main/java/dev/tripdraw/post/query/PostCustomRepository.java new file mode 100644 index 000000000..8cf61e940 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/query/PostCustomRepository.java @@ -0,0 +1,12 @@ +package dev.tripdraw.post.query; + +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.dto.PostSearchConditions; +import dev.tripdraw.post.dto.PostSearchPaging; + +import java.util.List; + +public interface PostCustomRepository { + + List findAllByConditions(PostSearchConditions conditions, PostSearchPaging paging); +} diff --git a/backend/src/main/java/dev/tripdraw/post/query/PostCustomRepositoryImpl.java b/backend/src/main/java/dev/tripdraw/post/query/PostCustomRepositoryImpl.java new file mode 100644 index 000000000..21a5af420 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/post/query/PostCustomRepositoryImpl.java @@ -0,0 +1,88 @@ +package dev.tripdraw.post.query; + +import static dev.tripdraw.post.domain.QPost.post; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.dto.PostSearchConditions; +import dev.tripdraw.post.dto.PostSearchPaging; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Set; + +@RequiredArgsConstructor +@Repository +public class PostCustomRepositoryImpl implements PostCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findAllByConditions(PostSearchConditions conditions, PostSearchPaging paging) { + // TODO: 2023/09/16 연령대, 성별 추가 + return jpaQueryFactory.selectFrom(post) + .leftJoin(post.point).fetchJoin() + .leftJoin(post.point.trip).fetchJoin() + .where( + postIdLt(paging.lastViewedId()), + yearIn(conditions.years()), + monthIn(conditions.months()), + dayOfWeekIn(conditions.daysOfWeek()), + hourIn(conditions.hours()), + addressLike(conditions.address()) + ) + .limit(paging.limit().longValue() + 1L) + .orderBy(post.id.desc()) + .fetch(); + } + + private BooleanExpression postIdLt(Long lastViewedId) { + if (lastViewedId == null) { + return null; + } + + return post.id.lt(lastViewedId); + } + + private BooleanExpression yearIn(Set years) { + if (years == null || years.isEmpty()) { + return null; + } + + return post.point.recordedAt.year().in(years); + } + + private BooleanExpression monthIn(Set months) { + if (months == null || months.isEmpty()) { + return null; + } + + return post.point.recordedAt.month().in(months); + } + + private BooleanExpression dayOfWeekIn(Set daysOfWeek) { + if (daysOfWeek == null || daysOfWeek.isEmpty()) { + return null; + } + + return post.point.recordedAt.dayOfWeek().in(daysOfWeek); + } + + private BooleanExpression hourIn(Set hours) { + if (hours == null || hours.isEmpty()) { + return null; + } + + return post.point.recordedAt.hour().in(hours); + } + + private BooleanExpression addressLike(String address) { + if (address == null || address.isEmpty()) { + return null; + } + + return post.address.like(address + "%"); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/application/TripDeleteEventHandler.java b/backend/src/main/java/dev/tripdraw/trip/application/TripDeleteEventHandler.java new file mode 100644 index 000000000..c23fbdfb3 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/application/TripDeleteEventHandler.java @@ -0,0 +1,21 @@ +package dev.tripdraw.trip.application; + +import dev.tripdraw.member.domain.MemberDeleteEvent; +import dev.tripdraw.trip.domain.TripRepository; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class TripDeleteEventHandler { + + private final TripRepository tripRepository; + + public TripDeleteEventHandler(TripRepository tripRepository) { + this.tripRepository = tripRepository; + } + + @EventListener + public void deletePostByMemberId(MemberDeleteEvent event) { + tripRepository.deleteByMemberId(event.memberId()); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/application/TripQueryService.java b/backend/src/main/java/dev/tripdraw/trip/application/TripQueryService.java new file mode 100644 index 000000000..34edc3b69 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/application/TripQueryService.java @@ -0,0 +1,25 @@ +package dev.tripdraw.trip.application; + +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.dto.TripSearchConditions; +import dev.tripdraw.trip.query.TripCustomRepository; +import dev.tripdraw.trip.query.TripPaging; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional +@Service +public class TripQueryService { + + private final TripCustomRepository tripCustomRepository; + + @Transactional(readOnly = true) + public List readAllByQueryConditions(TripSearchConditions tripSearchConditions, TripPaging tripPaging) { + return tripCustomRepository.findAllByConditions(tripSearchConditions, tripPaging); + } +} + diff --git a/backend/src/main/java/dev/tripdraw/trip/application/TripService.java b/backend/src/main/java/dev/tripdraw/trip/application/TripService.java new file mode 100644 index 000000000..a3910a2e2 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/application/TripService.java @@ -0,0 +1,125 @@ +package dev.tripdraw.trip.application; + +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.PointRepository; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.domain.TripUpdateEvent; +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.PointCreateResponse; +import dev.tripdraw.trip.dto.PointResponse; +import dev.tripdraw.trip.dto.TripCreateResponse; +import dev.tripdraw.trip.dto.TripResponse; +import dev.tripdraw.trip.dto.TripSearchConditions; +import dev.tripdraw.trip.dto.TripSearchRequest; +import dev.tripdraw.trip.dto.TripUpdateRequest; +import dev.tripdraw.trip.dto.TripsSearchResponse; +import dev.tripdraw.trip.dto.TripsSearchResponseOfMember; +import dev.tripdraw.trip.query.TripPaging; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional +@Service +public class TripService { + + private static final int FIRST_INDEX = 0; + + private final TripRepository tripRepository; + private final PointRepository pointRepository; + private final MemberRepository memberRepository; + private final TripQueryService tripQueryService; + private final ApplicationEventPublisher applicationEventPublisher; + + public TripCreateResponse create(LoginUser loginUser) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = Trip.from(member); + Trip savedTrip = tripRepository.save(trip); + return TripCreateResponse.from(savedTrip); + } + + public PointCreateResponse addPoint(LoginUser loginUser, PointCreateRequest pointCreateRequest) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(pointCreateRequest.tripId()); + trip.validateAuthorization(member); + + Point point = pointCreateRequest.toPoint(); + point.setTrip(trip); + pointRepository.save(point); + + return PointCreateResponse.from(point); + } + + public void deletePoint(LoginUser loginUser, Long pointId, Long tripId) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(tripId); + trip.validateAuthorization(member); + + Point point = pointRepository.getById(pointId); + pointRepository.delete(point); + } + + @Transactional(readOnly = true) + public TripResponse readTripById(LoginUser loginUser, Long id) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(id); + trip.validateAuthorization(member); + return TripResponse.from(trip); + } + + @Transactional(readOnly = true) + public TripsSearchResponseOfMember readAllTripsOf(LoginUser loginUser) { + Member member = memberRepository.getById(loginUser.memberId()); + List trips = tripRepository.findAllByMemberId(member.id()); + return TripsSearchResponseOfMember.from(trips); + } + + @Transactional(readOnly = true) + public TripsSearchResponse readAll(TripSearchRequest tripSearchRequest) { + TripSearchConditions condition = tripSearchRequest.toTripSearchConditions(); + TripPaging tripPaging = tripSearchRequest.toTripPaging(); + + List trips = tripQueryService.readAllByQueryConditions(condition, tripPaging); + + if (tripPaging.hasNextPage(trips.size())) { + return TripsSearchResponse.of(trips.subList(FIRST_INDEX, tripPaging.limit()), true); + } + return TripsSearchResponse.of(trips, false); + } + + public void updateTripById(LoginUser loginUser, Long tripId, TripUpdateRequest tripUpdateRequest) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(tripId); + trip.validateAuthorization(member); + + trip.changeName(tripUpdateRequest.name()); + trip.changeStatus(tripUpdateRequest.status()); + + applicationEventPublisher.publishEvent(new TripUpdateEvent(trip.id())); + } + + @Transactional(readOnly = true) + public PointResponse readPointByTripAndPointId(LoginUser loginUser, Long tripId, Long pointId) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(tripId); + trip.validateAuthorization(member); + + Point point = pointRepository.getById(pointId); + return PointResponse.from(point); + } + + public void delete(LoginUser loginUser, Long tripId) { + Member member = memberRepository.getById(loginUser.memberId()); + Trip trip = tripRepository.getById(tripId); + trip.validateAuthorization(member); + + tripRepository.delete(trip); + } +} diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/Point.java b/backend/src/main/java/dev/tripdraw/trip/domain/Point.java similarity index 60% rename from backend/src/main/java/dev/tripdraw/domain/trip/Point.java rename to backend/src/main/java/dev/tripdraw/trip/domain/Point.java index d4cc5e6bc..84e916d21 100644 --- a/backend/src/main/java/dev/tripdraw/domain/trip/Point.java +++ b/backend/src/main/java/dev/tripdraw/trip/domain/Point.java @@ -1,16 +1,17 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_DELETED; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_HAS_POST; +import static dev.tripdraw.trip.exception.TripExceptionType.POINT_ALREADY_HAS_POST; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import dev.tripdraw.domain.common.BaseEntity; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.common.entity.BaseEntity; +import dev.tripdraw.trip.exception.TripException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import java.time.LocalDateTime; import lombok.Getter; import lombok.NoArgsConstructor; @@ -39,23 +40,36 @@ public class Point extends BaseEntity { @Column(nullable = false) private LocalDateTime recordedAt; - @Column(nullable = false) - private Boolean isDeleted = false; + @JoinColumn(name = "trip_id") + @ManyToOne + private Trip trip; public Point(Double latitude, Double longitude, LocalDateTime recordedAt) { - this(null, latitude, longitude, false, recordedAt); + this(null, latitude, longitude, false, recordedAt, null); + } + + public Point(Double latitude, Double longitude, LocalDateTime recordedAt, Trip trip) { + this(null, latitude, longitude, false, recordedAt, trip); } - public Point(Long id, Double latitude, Double longitude, boolean hasPost, LocalDateTime recordedAt) { + public Point(Double latitude, Double longitude, boolean hasPost, LocalDateTime recordedAt) { + this(null, latitude, longitude, hasPost, recordedAt, null); + } + + public Point(Long id, Double latitude, Double longitude, boolean hasPost, LocalDateTime recordedAt, Trip trip) { this.id = id; this.latitude = latitude; this.longitude = longitude; this.hasPost = hasPost; this.recordedAt = recordedAt; + this.trip = trip; } - public Long id() { - return id; + public void setTrip(Trip trip) { + this.trip = trip; + if (!trip.contains(this)) { + trip.add(this); + } } public void registerPost() { @@ -68,16 +82,4 @@ private void validateNotPosted() { throw new TripException(POINT_ALREADY_HAS_POST); } } - - public void delete() { - validateNotDeleted(); - - isDeleted = true; - } - - private void validateNotDeleted() { - if (isDeleted) { - throw new TripException(POINT_ALREADY_DELETED); - } - } } diff --git a/backend/src/main/java/dev/tripdraw/trip/domain/PointRepository.java b/backend/src/main/java/dev/tripdraw/trip/domain/PointRepository.java new file mode 100644 index 000000000..ac4e94e36 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/domain/PointRepository.java @@ -0,0 +1,14 @@ +package dev.tripdraw.trip.domain; + +import static dev.tripdraw.trip.exception.TripExceptionType.POINT_NOT_FOUND; + +import dev.tripdraw.trip.exception.TripException; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PointRepository extends JpaRepository { + + default Point getById(Long id) { + return findById(id) + .orElseThrow(() -> new TripException(POINT_NOT_FOUND)); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/domain/Route.java b/backend/src/main/java/dev/tripdraw/trip/domain/Route.java new file mode 100644 index 000000000..0fa735423 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/domain/Route.java @@ -0,0 +1,32 @@ +package dev.tripdraw.trip.domain; + +import static jakarta.persistence.CascadeType.PERSIST; +import static jakarta.persistence.CascadeType.REMOVE; +import static jakarta.persistence.FetchType.LAZY; +import static lombok.AccessLevel.PROTECTED; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Accessors(fluent = true) +@Getter +@NoArgsConstructor(access = PROTECTED) +@Embeddable +public class Route { + + @OneToMany(mappedBy = "trip", fetch = LAZY, cascade = {PERSIST, REMOVE}) + private List points = new ArrayList<>(); + + public void add(Point point) { + points.add(point); + } + + public boolean contains(Point point) { + return points.contains(point); + } +} diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/Trip.java b/backend/src/main/java/dev/tripdraw/trip/domain/Trip.java similarity index 83% rename from backend/src/main/java/dev/tripdraw/domain/trip/Trip.java rename to backend/src/main/java/dev/tripdraw/trip/domain/Trip.java index d95634424..44b49fd9c 100644 --- a/backend/src/main/java/dev/tripdraw/domain/trip/Trip.java +++ b/backend/src/main/java/dev/tripdraw/trip/domain/Trip.java @@ -1,15 +1,15 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; -import static dev.tripdraw.domain.trip.TripStatus.ONGOING; -import static dev.tripdraw.exception.trip.TripExceptionType.NOT_AUTHORIZED_TO_TRIP; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_INVALID_STATUS; +import static dev.tripdraw.trip.domain.TripStatus.ONGOING; +import static dev.tripdraw.trip.exception.TripExceptionType.NOT_AUTHORIZED_TO_TRIP; +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_INVALID_STATUS; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import dev.tripdraw.domain.common.BaseEntity; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.common.entity.BaseEntity; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.trip.exception.TripException; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -72,6 +72,13 @@ public static Trip from(Member member) { public void add(Point point) { route.add(point); + if (point.trip() != this) { + point.setTrip(this); + } + } + + public boolean contains(Point point) { + return route.contains(point); } public void validateAuthorization(Member member) { @@ -95,10 +102,6 @@ public void changeName(String name) { this.name.change(name); } - public Point findPointById(Long pointId) { - return route.findPointById(pointId); - } - public void changeImageUrl(String imageUrl) { this.imageUrl = imageUrl; } @@ -133,14 +136,8 @@ public List getPointedLongitudes() { .toList(); } - public void deletePointById(Long pointId) { - route.deletePointById(pointId); - } - public List points() { - return route.points().stream() - .filter(point -> !point.isDeleted()) - .toList(); + return route.points(); } public String nameValue() { diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/TripName.java b/backend/src/main/java/dev/tripdraw/trip/domain/TripName.java similarity index 94% rename from backend/src/main/java/dev/tripdraw/domain/trip/TripName.java rename to backend/src/main/java/dev/tripdraw/trip/domain/TripName.java index 44c4f8afc..dc275406a 100644 --- a/backend/src/main/java/dev/tripdraw/domain/trip/TripName.java +++ b/backend/src/main/java/dev/tripdraw/trip/domain/TripName.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; import static lombok.AccessLevel.PROTECTED; diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/TripRepository.java b/backend/src/main/java/dev/tripdraw/trip/domain/TripRepository.java similarity index 58% rename from backend/src/main/java/dev/tripdraw/domain/trip/TripRepository.java rename to backend/src/main/java/dev/tripdraw/trip/domain/TripRepository.java index 3abfc0765..edb45c393 100644 --- a/backend/src/main/java/dev/tripdraw/domain/trip/TripRepository.java +++ b/backend/src/main/java/dev/tripdraw/trip/domain/TripRepository.java @@ -1,12 +1,13 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_NOT_FOUND; +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_NOT_FOUND; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.trip.exception.TripException; import java.util.List; 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 TripRepository extends JpaRepository { @@ -14,8 +15,13 @@ public interface TripRepository extends JpaRepository { void deleteByMemberId(Long memberId); + default Trip getById(Long id) { + return findById(id) + .orElseThrow(() -> new TripException(TRIP_NOT_FOUND)); + } + @Query("SELECT t FROM Trip t JOIN FETCH t.route.points where t.id = :tripId") - Optional findTripWithPoints(Long tripId); + Optional findTripWithPoints(@Param("tripId") Long tripId); default Trip getTripWithPoints(Long tripId) { return findTripWithPoints(tripId) diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/TripStatus.java b/backend/src/main/java/dev/tripdraw/trip/domain/TripStatus.java similarity index 59% rename from backend/src/main/java/dev/tripdraw/domain/trip/TripStatus.java rename to backend/src/main/java/dev/tripdraw/trip/domain/TripStatus.java index 7d1b6aa9c..0764edb7a 100644 --- a/backend/src/main/java/dev/tripdraw/domain/trip/TripStatus.java +++ b/backend/src/main/java/dev/tripdraw/trip/domain/TripStatus.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; public enum TripStatus { ONGOING, FINISHED diff --git a/backend/src/main/java/dev/tripdraw/domain/trip/TripUpdateEvent.java b/backend/src/main/java/dev/tripdraw/trip/domain/TripUpdateEvent.java similarity index 58% rename from backend/src/main/java/dev/tripdraw/domain/trip/TripUpdateEvent.java rename to backend/src/main/java/dev/tripdraw/trip/domain/TripUpdateEvent.java index 74c54c641..909f525f0 100644 --- a/backend/src/main/java/dev/tripdraw/domain/trip/TripUpdateEvent.java +++ b/backend/src/main/java/dev/tripdraw/trip/domain/TripUpdateEvent.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; public record TripUpdateEvent(Long tripId) { } diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/PointCreateRequest.java b/backend/src/main/java/dev/tripdraw/trip/dto/PointCreateRequest.java similarity index 92% rename from backend/src/main/java/dev/tripdraw/dto/trip/PointCreateRequest.java rename to backend/src/main/java/dev/tripdraw/trip/dto/PointCreateRequest.java index 44f9636f2..6d7dff2bd 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/PointCreateRequest.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/PointCreateRequest.java @@ -1,9 +1,9 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; import static com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING; import com.fasterxml.jackson.annotation.JsonFormat; -import dev.tripdraw.domain.trip.Point; +import dev.tripdraw.trip.domain.Point; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/PointCreateResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/PointCreateResponse.java similarity index 80% rename from backend/src/main/java/dev/tripdraw/dto/trip/PointCreateResponse.java rename to backend/src/main/java/dev/tripdraw/trip/dto/PointCreateResponse.java index de0e6ae6f..bc56aae64 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/PointCreateResponse.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/PointCreateResponse.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; -import dev.tripdraw.domain.trip.Point; +import dev.tripdraw.trip.domain.Point; import io.swagger.v3.oas.annotations.media.Schema; public record PointCreateResponse( diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/PointResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/PointResponse.java similarity index 93% rename from backend/src/main/java/dev/tripdraw/dto/trip/PointResponse.java rename to backend/src/main/java/dev/tripdraw/trip/dto/PointResponse.java index 295c384a2..64756af23 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/PointResponse.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/PointResponse.java @@ -1,9 +1,9 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; import static com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING; import com.fasterxml.jackson.annotation.JsonFormat; -import dev.tripdraw.domain.trip.Point; +import dev.tripdraw.trip.domain.Point; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/TripCreateResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripCreateResponse.java similarity index 80% rename from backend/src/main/java/dev/tripdraw/dto/trip/TripCreateResponse.java rename to backend/src/main/java/dev/tripdraw/trip/dto/TripCreateResponse.java index a545839d4..81c442cb2 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/TripCreateResponse.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripCreateResponse.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; -import dev.tripdraw.domain.trip.Trip; +import dev.tripdraw.trip.domain.Trip; import io.swagger.v3.oas.annotations.media.Schema; public record TripCreateResponse( diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/TripResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripResponse.java similarity index 77% rename from backend/src/main/java/dev/tripdraw/dto/trip/TripResponse.java rename to backend/src/main/java/dev/tripdraw/trip/dto/TripResponse.java index 79fe8d083..51051c363 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/TripResponse.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripResponse.java @@ -1,9 +1,10 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripStatus; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripStatus; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; +import java.util.Objects; public record TripResponse( @Schema(description = "여행 Id", example = "1") @@ -25,14 +26,16 @@ public record TripResponse( String routeImageUrl ) { + private static final String EMPTY_IMAGE_URL = ""; + public static TripResponse from(Trip trip) { return new TripResponse( trip.id(), trip.nameValue(), generateRoute(trip), trip.status(), - trip.imageUrl(), - trip.routeImageUrl() + Objects.requireNonNullElse(trip.imageUrl(), EMPTY_IMAGE_URL), + Objects.requireNonNullElse(trip.routeImageUrl(), EMPTY_IMAGE_URL) ); } diff --git a/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchConditions.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchConditions.java new file mode 100644 index 000000000..785818f63 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchConditions.java @@ -0,0 +1,16 @@ +package dev.tripdraw.trip.dto; + +import lombok.Builder; + +import java.util.Set; + +@Builder +public record TripSearchConditions( + Set years, + Set months, + Set daysOfWeek, + Set ageRanges, + Set genders, + String address +) { +} diff --git a/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchPaging.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchPaging.java new file mode 100644 index 000000000..da83f1bdd --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchPaging.java @@ -0,0 +1,9 @@ +package dev.tripdraw.trip.dto; + +import dev.tripdraw.trip.query.TripPaging; + +public record TripSearchPaging(Long lastViewedId, Integer limit) { + public TripPaging toTripPaging() { + return new TripPaging(lastViewedId, limit); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchRequest.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchRequest.java new file mode 100644 index 000000000..2688c4b35 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchRequest.java @@ -0,0 +1,33 @@ +package dev.tripdraw.trip.dto; + +import dev.tripdraw.trip.query.TripPaging; +import java.util.Set; +import lombok.Builder; + +@Builder +public record TripSearchRequest( + Set years, + Set months, + Set daysOfWeek, + Set ageRanges, + Set genders, + String address, + Long lastViewedId, + Integer limit +) { + + public TripSearchConditions toTripSearchConditions() { + return TripSearchConditions.builder() + .years(years) + .months(months) + .daysOfWeek(daysOfWeek) + .ageRanges(ageRanges) + .genders(genders) + .address(address) + .build(); + } + + public TripPaging toTripPaging() { + return new TripPaging(lastViewedId, limit); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchResponse.java new file mode 100644 index 000000000..62a527001 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchResponse.java @@ -0,0 +1,40 @@ +package dev.tripdraw.trip.dto; + +import dev.tripdraw.trip.domain.Trip; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import java.util.Objects; + +public record TripSearchResponse( + @Schema(description = "여행 Id", example = "1") + Long tripId, + + @Schema(description = "여행명", example = "통후추의 여행") + String name, + + @Schema(description = "이미지 주소", example = "https://tripdraw.site/post-images/cd678ca2-30d5-11ee-be56-0242ac120002.jpg") + String imageUrl, + + @Schema(description = "경로 이미지 주소", example = "https://tripdraw.site/route-images/cd678ca2-30d5-11ee-be56-0242ac120002.png") + String routeImageUrl, + + @Schema(description = "여행 시작 시각", example = "2023-07-23T19:48:27") + LocalDateTime startTime, + + @Schema(description = "여행 종료 시각", example = "2023-07-23T19:48:27") + LocalDateTime endTime +) { + + private static final String EMPTY_IMAGE_URL = ""; + + public static TripSearchResponse from(Trip trip) { + return new TripSearchResponse( + trip.id(), + trip.nameValue(), + Objects.requireNonNullElse(trip.imageUrl(), EMPTY_IMAGE_URL), + Objects.requireNonNullElse(trip.routeImageUrl(), EMPTY_IMAGE_URL), + trip.createdAt(), + trip.updatedAt() + ); + } +} diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/TripSearchResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchResponseOfMember.java similarity index 72% rename from backend/src/main/java/dev/tripdraw/dto/trip/TripSearchResponse.java rename to backend/src/main/java/dev/tripdraw/trip/dto/TripSearchResponseOfMember.java index 98ae56860..a801c6f9f 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/TripSearchResponse.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripSearchResponseOfMember.java @@ -1,10 +1,10 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; -import dev.tripdraw.domain.trip.Trip; +import dev.tripdraw.trip.domain.Trip; import io.swagger.v3.oas.annotations.media.Schema; import java.util.Objects; -public record TripSearchResponse( +public record TripSearchResponseOfMember( @Schema(description = "여행 Id", example = "1") Long tripId, @@ -20,12 +20,12 @@ public record TripSearchResponse( private static final String EMPTY_IMAGE_URL = ""; - public static TripSearchResponse from(Trip trip) { - return new TripSearchResponse( + public static TripSearchResponseOfMember from(Trip trip) { + return new TripSearchResponseOfMember( trip.id(), trip.nameValue(), Objects.requireNonNullElse(trip.imageUrl(), EMPTY_IMAGE_URL), - Objects.requireNonNullElse(trip.imageUrl(), EMPTY_IMAGE_URL) + Objects.requireNonNullElse(trip.routeImageUrl(), EMPTY_IMAGE_URL) ); } } diff --git a/backend/src/main/java/dev/tripdraw/dto/trip/TripUpdateRequest.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripUpdateRequest.java similarity index 81% rename from backend/src/main/java/dev/tripdraw/dto/trip/TripUpdateRequest.java rename to backend/src/main/java/dev/tripdraw/trip/dto/TripUpdateRequest.java index 5e662f6f8..ae57a20df 100644 --- a/backend/src/main/java/dev/tripdraw/dto/trip/TripUpdateRequest.java +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripUpdateRequest.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; -import dev.tripdraw.domain.trip.TripStatus; +import dev.tripdraw.trip.domain.TripStatus; import io.swagger.v3.oas.annotations.media.Schema; public record TripUpdateRequest( diff --git a/backend/src/main/java/dev/tripdraw/trip/dto/TripsSearchResponse.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripsSearchResponse.java new file mode 100644 index 000000000..0f08a8057 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripsSearchResponse.java @@ -0,0 +1,17 @@ +package dev.tripdraw.trip.dto; + +import dev.tripdraw.trip.domain.Trip; +import java.util.List; + +public record TripsSearchResponse( + List trips, + boolean hasNextPage +) { + + public static TripsSearchResponse of(List trips, boolean hasNextPage) { + List tripsSearchResponse = trips.stream() + .map(TripSearchResponse::from) + .toList(); + return new TripsSearchResponse(tripsSearchResponse, hasNextPage); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/dto/TripsSearchResponseOfMember.java b/backend/src/main/java/dev/tripdraw/trip/dto/TripsSearchResponseOfMember.java new file mode 100644 index 000000000..ab48cb05b --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/dto/TripsSearchResponseOfMember.java @@ -0,0 +1,20 @@ +package dev.tripdraw.trip.dto; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +import dev.tripdraw.trip.domain.Trip; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +public record TripsSearchResponseOfMember( + @Schema(description = "여행 목록") + List trips +) { + + public static TripsSearchResponseOfMember from(List trips) { + return trips.stream() + .map(TripSearchResponseOfMember::from) + .collect(collectingAndThen(toList(), TripsSearchResponseOfMember::new)); + } +} diff --git a/backend/src/main/java/dev/tripdraw/exception/trip/TripException.java b/backend/src/main/java/dev/tripdraw/trip/exception/TripException.java similarity index 51% rename from backend/src/main/java/dev/tripdraw/exception/trip/TripException.java rename to backend/src/main/java/dev/tripdraw/trip/exception/TripException.java index 71555d645..fb0c73832 100644 --- a/backend/src/main/java/dev/tripdraw/exception/trip/TripException.java +++ b/backend/src/main/java/dev/tripdraw/trip/exception/TripException.java @@ -1,7 +1,7 @@ -package dev.tripdraw.exception.trip; +package dev.tripdraw.trip.exception; -import dev.tripdraw.exception.common.BaseException; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.BaseException; +import dev.tripdraw.common.exception.ExceptionType; public class TripException extends BaseException { diff --git a/backend/src/main/java/dev/tripdraw/exception/trip/TripExceptionType.java b/backend/src/main/java/dev/tripdraw/trip/exception/TripExceptionType.java similarity index 87% rename from backend/src/main/java/dev/tripdraw/exception/trip/TripExceptionType.java rename to backend/src/main/java/dev/tripdraw/trip/exception/TripExceptionType.java index d1984eb32..1d057f31d 100644 --- a/backend/src/main/java/dev/tripdraw/exception/trip/TripExceptionType.java +++ b/backend/src/main/java/dev/tripdraw/trip/exception/TripExceptionType.java @@ -1,22 +1,22 @@ -package dev.tripdraw.exception.trip; +package dev.tripdraw.trip.exception; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; -import dev.tripdraw.exception.common.ExceptionType; +import dev.tripdraw.common.exception.ExceptionType; import org.springframework.http.HttpStatus; public enum TripExceptionType implements ExceptionType { TRIP_NOT_FOUND(NOT_FOUND, "존재하지 않는 여행입니다."), NOT_AUTHORIZED_TO_TRIP(FORBIDDEN, "해당 여행에 대한 접근 권한이 없습니다."), - POINT_NOT_IN_TRIP(NOT_FOUND, "해당 여행에 존재하지 않는 위치정보입니다."), POINT_ALREADY_DELETED(CONFLICT, "이미 삭제된 위치정보입니다."), POINT_NOT_FOUND(NOT_FOUND, "존재하지 않는 위치입니다."), TRIP_INVALID_STATUS(BAD_REQUEST, "잘못된 여행 상태입니다."), POINT_ALREADY_HAS_POST(CONFLICT, "이미 감상이 등록된 위치입니다."), TRIP_ALREADY_DELETED(CONFLICT, "이미 삭제된 여행입니다."), + INVALID_TRIP_SEARCH(BAD_REQUEST, "유효하지 않은 여행 조회 조건입니다."), ; private final HttpStatus httpStatus; diff --git a/backend/src/main/java/dev/tripdraw/presentation/controller/TripController.java b/backend/src/main/java/dev/tripdraw/trip/presentation/TripController.java similarity index 72% rename from backend/src/main/java/dev/tripdraw/presentation/controller/TripController.java rename to backend/src/main/java/dev/tripdraw/trip/presentation/TripController.java index 2941132d7..7e7458330 100644 --- a/backend/src/main/java/dev/tripdraw/presentation/controller/TripController.java +++ b/backend/src/main/java/dev/tripdraw/trip/presentation/TripController.java @@ -1,19 +1,21 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.trip.presentation; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.NO_CONTENT; -import dev.tripdraw.application.TripService; -import dev.tripdraw.config.swagger.SwaggerAuthorizationRequired; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.dto.trip.PointCreateRequest; -import dev.tripdraw.dto.trip.PointCreateResponse; -import dev.tripdraw.dto.trip.PointResponse; -import dev.tripdraw.dto.trip.TripCreateResponse; -import dev.tripdraw.dto.trip.TripResponse; -import dev.tripdraw.dto.trip.TripUpdateRequest; -import dev.tripdraw.dto.trip.TripsSearchResponse; -import dev.tripdraw.presentation.member.Auth; +import dev.tripdraw.common.auth.Auth; +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.common.swagger.SwaggerAuthorizationRequired; +import dev.tripdraw.trip.application.TripService; +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.PointCreateResponse; +import dev.tripdraw.trip.dto.PointResponse; +import dev.tripdraw.trip.dto.TripCreateResponse; +import dev.tripdraw.trip.dto.TripResponse; +import dev.tripdraw.trip.dto.TripSearchRequest; +import dev.tripdraw.trip.dto.TripUpdateRequest; +import dev.tripdraw.trip.dto.TripsSearchResponse; +import dev.tripdraw.trip.dto.TripsSearchResponseOfMember; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -76,10 +78,10 @@ public ResponseEntity readPointById( return ResponseEntity.ok(response); } - @Operation(summary = "여행 조회 API", description = "단일 여행의 정보를 조회합니다.") + @Operation(summary = "나의 여행 조회 API", description = "회원 한 명의 단일 여행 정보를 조회합니다.") @ApiResponse( responseCode = "200", - description = "여행 조회 성공." + description = "나의 여행 조회 성공." ) @GetMapping("/trips/{tripId}") public ResponseEntity readById(@Auth LoginUser loginUser, @PathVariable Long tripId) { @@ -102,14 +104,28 @@ public ResponseEntity deletePoint( return ResponseEntity.noContent().build(); } - @Operation(summary = "여행 전체 조회 API", description = "모든 여행의 정보를 조회합니다.") + @Operation(summary = "나의 여행 전체 조회 API", description = "회원 한 명의 모든 여행 정보를 조회합니다.") @ApiResponse( responseCode = "200", - description = "여행 전체 조회 성공." + description = "나의 여행 전체 조회 성공." + ) + @GetMapping("/trips/me") + public ResponseEntity readAllOf(@Auth LoginUser loginUser) { + TripsSearchResponseOfMember response = tripService.readAllTripsOf(loginUser); + return ResponseEntity.ok(response); + } + + @Operation(summary = "모든 회원 여행 전체 조회 API", description = "모든 회원의 여행 정보를 조건에 따라 조회합니다.") + @ApiResponse( + responseCode = "200", + description = "모든 회원 여행 전체 조회 성공." ) @GetMapping("/trips") - public ResponseEntity readAll(@Auth LoginUser loginUser) { - TripsSearchResponse response = tripService.readAllTrips(loginUser); + public ResponseEntity readAll( + @Auth LoginUser loginUser, + TripSearchRequest tripSearchRequest + ) { + TripsSearchResponse response = tripService.readAll(tripSearchRequest); return ResponseEntity.ok(response); } diff --git a/backend/src/main/java/dev/tripdraw/trip/query/TripCustomRepository.java b/backend/src/main/java/dev/tripdraw/trip/query/TripCustomRepository.java new file mode 100644 index 000000000..8c89d9b70 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/query/TripCustomRepository.java @@ -0,0 +1,11 @@ +package dev.tripdraw.trip.query; + +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.dto.TripSearchConditions; + +import java.util.List; + +public interface TripCustomRepository { + + List findAllByConditions(TripSearchConditions tripSearchConditions, TripPaging tripPaging); +} diff --git a/backend/src/main/java/dev/tripdraw/trip/query/TripCustomRepositoryImpl.java b/backend/src/main/java/dev/tripdraw/trip/query/TripCustomRepositoryImpl.java new file mode 100644 index 000000000..557c5ba16 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/query/TripCustomRepositoryImpl.java @@ -0,0 +1,77 @@ +package dev.tripdraw.trip.query; + +import static dev.tripdraw.post.domain.QPost.post; +import static dev.tripdraw.trip.domain.QPoint.point; +import static dev.tripdraw.trip.domain.QTrip.trip; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.dto.TripSearchConditions; +import io.micrometer.common.util.StringUtils; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.util.CollectionUtils; + +@RequiredArgsConstructor +@Repository +public class TripCustomRepositoryImpl implements TripCustomRepository { + + private final JPAQueryFactory query; + + @Override + public List findAllByConditions(TripSearchConditions tripSearchConditions, TripPaging tripPaging) { + return query + .selectFrom(trip) + .distinct() + .join(post).on(trip.id.eq(post.tripId)) + .join(point).on(post.point.id.eq(point.id)) + .where( + tripIdLt(tripPaging.lastViewedId()), + yearIn(tripSearchConditions.years()), + monthIn(tripSearchConditions.months()), + dayOfWeekIn(tripSearchConditions.daysOfWeek()), + addressLike(tripSearchConditions.address()) + ) + .orderBy(trip.id.desc()) + .limit(tripPaging.limit().longValue() + 1L) + .fetch(); + } + + private BooleanExpression tripIdLt(Long lastViewedId) { + if (lastViewedId == null) { + return null; + } + return trip.id.lt(lastViewedId); + } + + private BooleanExpression yearIn(Set years) { + if (CollectionUtils.isEmpty(years)) { + return null; + } + return point.recordedAt.year().in(years); + } + + private BooleanExpression monthIn(Set months) { + if (CollectionUtils.isEmpty(months)) { + return null; + } + return point.recordedAt.month().in(months); + } + + private BooleanExpression dayOfWeekIn(Set daysOfWeek) { + if (CollectionUtils.isEmpty(daysOfWeek)) { + return null; + } + return point.recordedAt.dayOfWeek().in(daysOfWeek); + } + + private BooleanExpression addressLike(String address) { + if (StringUtils.isBlank(address)) { + return null; + } + return post.address.like(address + "%"); + } +} diff --git a/backend/src/main/java/dev/tripdraw/trip/query/TripPaging.java b/backend/src/main/java/dev/tripdraw/trip/query/TripPaging.java new file mode 100644 index 000000000..650c8c3e7 --- /dev/null +++ b/backend/src/main/java/dev/tripdraw/trip/query/TripPaging.java @@ -0,0 +1,18 @@ +package dev.tripdraw.trip.query; + +public record TripPaging(Long lastViewedId, Integer limit) { + private static final int LIMIT_MAXIMUM = 100; + + public TripPaging(Long lastViewedId, Integer limit) { + this.lastViewedId = lastViewedId; + this.limit = ceil(limit); + } + + private int ceil(Integer limit) { + return Math.min(limit, LIMIT_MAXIMUM); + } + + public boolean hasNextPage(int size) { + return limit < size; + } +} diff --git a/backend/src/main/resources/application-local.yml b/backend/src/main/resources/application-local.yml index 89403e743..6d470d991 100644 --- a/backend/src/main/resources/application-local.yml +++ b/backend/src/main/resources/application-local.yml @@ -51,17 +51,12 @@ trip: route: jwt: - secret-key: ItIsALocalKeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2222222222222222222222222222222222eeeeeeeeeeyXD - expiration-time: 172800000 # 48시간 millisecond - -management: - endpoint: - health: - enabled: true - endpoints: - web: - exposure: - include: prometheus + access: + secret-key: ACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKEN + expiration-time: 1800000 # 30 minutes + refresh: + secret-key: REFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFR + expiration-time: 1209600000 # 14 days oauth: kakao: diff --git a/backend/src/main/resources/db/migration/h2/V7__modifyPoint.sql b/backend/src/main/resources/db/migration/h2/V7__modifyPoint.sql new file mode 100644 index 000000000..d4d093cde --- /dev/null +++ b/backend/src/main/resources/db/migration/h2/V7__modifyPoint.sql @@ -0,0 +1 @@ +alter table point drop column is_deleted; diff --git a/backend/src/main/resources/db/migration/h2/V8__addRefreshTokenTable.sql b/backend/src/main/resources/db/migration/h2/V8__addRefreshTokenTable.sql new file mode 100644 index 000000000..3c77cda90 --- /dev/null +++ b/backend/src/main/resources/db/migration/h2/V8__addRefreshTokenTable.sql @@ -0,0 +1,8 @@ +CREATE TABLE `refresh_token` +( + `refresh_token_id` BIGINT PRIMARY KEY AUTO_INCREMENT, + `member_id` BIGINT, + `token` TEXT, + `created_at` TIMESTAMP, + `updated_at` TIMESTAMP +); diff --git a/backend/src/main/resources/db/migration/mysql/V7__modifyPoint.sql b/backend/src/main/resources/db/migration/mysql/V7__modifyPoint.sql new file mode 100644 index 000000000..d4d093cde --- /dev/null +++ b/backend/src/main/resources/db/migration/mysql/V7__modifyPoint.sql @@ -0,0 +1 @@ +alter table point drop column is_deleted; diff --git a/backend/src/main/resources/db/migration/mysql/V8__addRefreshTokenTable.sql b/backend/src/main/resources/db/migration/mysql/V8__addRefreshTokenTable.sql new file mode 100644 index 000000000..3c77cda90 --- /dev/null +++ b/backend/src/main/resources/db/migration/mysql/V8__addRefreshTokenTable.sql @@ -0,0 +1,8 @@ +CREATE TABLE `refresh_token` +( + `refresh_token_id` BIGINT PRIMARY KEY AUTO_INCREMENT, + `member_id` BIGINT, + `token` TEXT, + `created_at` TIMESTAMP, + `updated_at` TIMESTAMP +); diff --git a/backend/src/test/java/dev/tripdraw/application/AuthServiceTest.java b/backend/src/test/java/dev/tripdraw/application/AuthServiceTest.java deleted file mode 100644 index 54e63b0f4..000000000 --- a/backend/src/test/java/dev/tripdraw/application/AuthServiceTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.exception.member.MemberExceptionType.DUPLICATE_NICKNAME; -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.when; - -import dev.tripdraw.application.oauth.OauthClientProvider; -import dev.tripdraw.common.TestKakaoApiClient; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.dto.auth.OauthRequest; -import dev.tripdraw.dto.auth.OauthResponse; -import dev.tripdraw.dto.auth.RegisterRequest; -import dev.tripdraw.exception.member.MemberException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; - -@ServiceTest -class AuthServiceTest { - - @Autowired - private AuthService authService; - - @MockBean - OauthClientProvider oauthClientProvider; - - @Autowired - private MemberRepository memberRepository; - - @BeforeEach - void setUp() { - when(oauthClientProvider.provide(KAKAO)).thenReturn(new TestKakaoApiClient()); - } - - @Test - void 가입된_회원이_카카오_소셜_로그인하면_토큰이_포함된_응답을_반환한다() { - // given - memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - OauthRequest oauthRequest = new OauthRequest(KAKAO, "oauth.kakao.token"); - - // when - OauthResponse response = authService.login(oauthRequest); - - // then - assertThat(response.accessToken()).isNotEmpty(); - } - - @Test - void 신규_회원이_로그인하면_회원을_저장하고_빈_토큰이_포함된_응답을_반환한다() { - // given - OauthRequest oauthRequest = new OauthRequest(KAKAO, "oauth.kakao.token"); - - // when - OauthResponse response = authService.login(oauthRequest); - - // then - assertThat(response.accessToken()).isEmpty(); - } - - @Test - void 신규_회원의_닉네임을_등록하면_토큰이_포함된_응답을_반환한다() { - // given - Member member = Member.of("kakaoId", KAKAO); - memberRepository.save(member); - - RegisterRequest registerRequest = new RegisterRequest("통후추", KAKAO, "oauth.kakao.token"); - - // when - OauthResponse response = authService.register(registerRequest); - - // then - assertThat(response.accessToken()).isNotEmpty(); - } - - @Test - void 신규_회원의_닉네임을_등록할_때_회원이_존재하지_않으면_예외가_발생한다() { - // given - RegisterRequest registerRequest = new RegisterRequest("저장안된후추", KAKAO, "oauth.kakao.token"); - - // expect - assertThatThrownBy(() -> authService.register(registerRequest)) - .isInstanceOf(MemberException.class) - .hasMessage(MEMBER_NOT_FOUND.message()); - } - - @Test - void 신규_회원의_닉네임을_등록할_때_이미_존재하는_닉네임이면_예외가_발생한다() { - // given - memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - RegisterRequest registerRequest = new RegisterRequest("통후추", KAKAO, "oauth.kakao.token"); - - // expect - assertThatThrownBy(() -> authService.register(registerRequest)) - .isInstanceOf(MemberException.class) - .hasMessage(DUPLICATE_NICKNAME.message()); - } -} diff --git a/backend/src/test/java/dev/tripdraw/application/AuthTokenManagerTest.java b/backend/src/test/java/dev/tripdraw/application/AuthTokenManagerTest.java deleted file mode 100644 index 9f99127a0..000000000 --- a/backend/src/test/java/dev/tripdraw/application/AuthTokenManagerTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.tripdraw.application; - -import static org.assertj.core.api.Assertions.assertThat; - -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.application.oauth.JwtTokenProvider; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class AuthTokenManagerTest { - - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; - private JwtTokenProvider jwtTokenProvider; - - @BeforeEach - void setUp() { - jwtTokenProvider = new JwtTokenProvider( - "test2222222222eeeeeeeeeeee222222222asdfasdfasdfasdfasdssssssssssaaaaaaaaaavvvvvvvfsdfsf2eeeeeeeeeeeee" - ); - } - - @Test - void 회원_ID를_입력_받아_토큰을_생성한다() { - // given - AuthTokenManager authTokenManager = new AuthTokenManager(jwtTokenProvider, ACCESS_TOKEN_EXPIRE_TIME); - - // when - String accessToken = authTokenManager.generate(1L); - - // then - assertThat(accessToken).isNotEmpty(); - } - - @Test - void 토큰에서_회원_ID를_추출한다() { - // given - AuthTokenManager authTokenManager = new AuthTokenManager(jwtTokenProvider, ACCESS_TOKEN_EXPIRE_TIME); - String accessToken = authTokenManager.generate(1L); - - // when - Long memberId = authTokenManager.extractMemberId(accessToken); - - // then - assertThat(memberId).isEqualTo(1L); - } -} diff --git a/backend/src/test/java/dev/tripdraw/application/JwtTokenProviderTest.java b/backend/src/test/java/dev/tripdraw/application/JwtTokenProviderTest.java deleted file mode 100644 index 011dbe67c..000000000 --- a/backend/src/test/java/dev/tripdraw/application/JwtTokenProviderTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.exception.auth.AuthExceptionType.EXPIRED_TOKEN; -import static dev.tripdraw.exception.auth.AuthExceptionType.INVALID_TOKEN; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import dev.tripdraw.application.oauth.JwtTokenProvider; -import dev.tripdraw.exception.auth.AuthException; -import java.time.Instant; -import java.util.Date; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class JwtTokenProviderTest { - - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; - private static final Date EXPIRED_AT = new Date(new Date().getTime() + ACCESS_TOKEN_EXPIRE_TIME); - - private JwtTokenProvider jwtTokenProvider; - - @BeforeEach - void setUp() { - String secretKey = "test2222222222eeeeeeeeeeee222222222asdfasdfasdfasdfasdssssssssssaaaaaaaaaavvvvvvvfsdfsf2eeeeeeeeeeeee"; - jwtTokenProvider = new JwtTokenProvider(secretKey); - } - - @Test - void 토큰_정보를_추출한다() { - // given - String accessToken = jwtTokenProvider.generate("memberId", EXPIRED_AT); - - // when - String subject = jwtTokenProvider.extractSubject(accessToken); - - // then - assertThat(subject).isEqualTo("memberId"); - } - - @Test - void 대상과_만료기한을_입력_받아_토큰을_생성한다() { - // given - String subject = "memberId"; - - // when - String accessToken = jwtTokenProvider.generate(subject, EXPIRED_AT); - - // then - assertThat(accessToken).isNotEmpty(); - } - - @Test - void 유효하지_않은_토큰의_정보를_추출할_때_예외를_발생시킨다() { - // given - String invalidToken = "Invalid.Token.XD"; - - // expect - assertThatThrownBy(() -> jwtTokenProvider.extractSubject(invalidToken)) - .isInstanceOf(AuthException.class) - .hasMessage(INVALID_TOKEN.message()); - } - - @Test - void 만료된_토큰의_정보를_추출할_때_예외를_발생시킨다() { - // given - Date expiredDate = Date.from(Instant.now().minusSeconds(1)); - String expiredToken = jwtTokenProvider.generate("memberId", expiredDate); - - // expect - assertThatThrownBy(() -> jwtTokenProvider.extractSubject(expiredToken)) - .isInstanceOf(AuthException.class) - .hasMessage(EXPIRED_TOKEN.message()); - } -} diff --git a/backend/src/test/java/dev/tripdraw/application/MemberServiceTest.java b/backend/src/test/java/dev/tripdraw/application/MemberServiceTest.java deleted file mode 100644 index 27f8481a9..000000000 --- a/backend/src/test/java/dev/tripdraw/application/MemberServiceTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; -import static java.lang.Long.MIN_VALUE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripName; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.member.MemberSearchResponse; -import dev.tripdraw.exception.member.MemberException; -import java.time.LocalDateTime; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -@ServiceTest -class MemberServiceTest { - - @Autowired - private MemberService memberService; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private AuthTokenManager authTokenManager; - - @Autowired - private TripRepository tripRepository; - - @Autowired - private PostRepository postRepository; - - private Member member; - private String code; - private Trip trip; - private Post post; - - @BeforeEach - void setUp() { - member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - code = authTokenManager.generate(member.id()); - - trip = tripRepository.save(new Trip(TripName.from("통후추의 여행"), member)); - Point point = new Point(3.14, 5.25, LocalDateTime.now()); - trip.add(point); - post = postRepository.save(new Post( - "제목", - point, - "위치", - "오늘은 날씨가 좋네요.", - member, - trip.id() - )); - } - - @Test - void code를_입력_받아_사용자를_조회한다() { - // given & when - MemberSearchResponse response = memberService.findByCode(code); - - // expect - assertThat(response).usingRecursiveComparison().isEqualTo( - new MemberSearchResponse(member.id(), "통후추") - ); - } - - @Test - void code를_입력_받아_사용자를_조회할_때_이미_삭제된_사용자라면_예외를_발생시킨다() { - // given - Member member = memberRepository.save(new Member("순후추", "kakaoId", KAKAO)); - String code = authTokenManager.generate(member.id()); - - memberRepository.deleteById(member.id()); - - // expect - assertThatThrownBy(() -> memberService.findByCode(code)) - .isInstanceOf(MemberException.class) - .hasMessage(MEMBER_NOT_FOUND.message()); - } - - @Test - void code를_입력_받아_사용자를_조회할_때_존재하지_않는_사용자라면_예외를_발생시킨다() { - String nonExistentCode = authTokenManager.generate(MIN_VALUE); - - // expect - assertThatThrownBy(() -> memberService.findByCode(nonExistentCode)) - .isInstanceOf(MemberException.class) - .hasMessage(MEMBER_NOT_FOUND.message()); - } - - @Test - void code를_입력_받아_사용자를_삭제한다() { - // given & when - memberService.deleteByCode(code); - - // then - assertSoftly(softly -> { - softly.assertThat(memberRepository.findById(member.id())).isEmpty(); - softly.assertThat(tripRepository.findById(trip.id())).isEmpty(); - softly.assertThat(postRepository.findById(post.id())).isEmpty(); - }); - } -} diff --git a/backend/src/test/java/dev/tripdraw/presentation/member/AuthExtractorTest.java b/backend/src/test/java/dev/tripdraw/auth/application/AuthExtractorTest.java similarity index 62% rename from backend/src/test/java/dev/tripdraw/presentation/member/AuthExtractorTest.java rename to backend/src/test/java/dev/tripdraw/auth/application/AuthExtractorTest.java index d72f2f68f..880c8c97b 100644 --- a/backend/src/test/java/dev/tripdraw/presentation/member/AuthExtractorTest.java +++ b/backend/src/test/java/dev/tripdraw/auth/application/AuthExtractorTest.java @@ -1,20 +1,19 @@ -package dev.tripdraw.presentation.member; +package dev.tripdraw.auth.application; -import static dev.tripdraw.exception.auth.AuthExceptionType.INVALID_AUTH_HEADER; -import static dev.tripdraw.exception.auth.AuthExceptionType.INVALID_TOKEN; +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_AUTH_HEADER; +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_TOKEN; +import static dev.tripdraw.test.fixture.AuthFixture.테스트_ACCESS_TOKEN_설정; +import static dev.tripdraw.test.fixture.AuthFixture.테스트_REFRESH_TOKEN_설정; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.http.HttpHeaders.AUTHORIZATION; -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.application.oauth.JwtTokenProvider; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.exception.auth.AuthException; +import dev.tripdraw.auth.exception.AuthException; +import dev.tripdraw.common.auth.LoginUser; import jakarta.servlet.http.HttpServletRequest; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -23,21 +22,16 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class AuthExtractorTest { - private static final String TEST_SECRET_KEY = "ItIsALocalKeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2222222222222222222222222222222222eeeeeeeeeeyXD"; - private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; - - private AuthTokenManager authTokenManager; - - @BeforeEach - void setUp() { - authTokenManager = new AuthTokenManager(new JwtTokenProvider(TEST_SECRET_KEY), ACCESS_TOKEN_EXPIRE_TIME); - } + private final JwtTokenProvider jwtTokenProvider = new JwtTokenProvider( + 테스트_ACCESS_TOKEN_설정(), + 테스트_REFRESH_TOKEN_설정() + ); + private final AuthExtractor authExtractor = new AuthExtractor(jwtTokenProvider); @Test void 요청_헤더에서_LoginUser를_추출한다() { // given - AuthExtractor authExtractor = new AuthExtractor(authTokenManager); - String accessToken = authTokenManager.generate(1L); + String accessToken = jwtTokenProvider.generateAccessToken("1"); HttpServletRequest request = mock(HttpServletRequest.class); String encoded = "Bearer " + accessToken; when(request.getHeader(AUTHORIZATION)).thenReturn(encoded); @@ -52,11 +46,8 @@ void setUp() { @Test void 요청_헤더에_Bearer_형식이_아닌_다른_인증정보를_사용하는_경우_예외가_발생한다() { // given - AuthExtractor authExtractor = new AuthExtractor( - new AuthTokenManager(new JwtTokenProvider(TEST_SECRET_KEY), ACCESS_TOKEN_EXPIRE_TIME) - ); HttpServletRequest request = mock(HttpServletRequest.class); - String encoded = "Basic eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjkxNTE1Mzk2fQ.WEDBjEXfIAd4MaUTK29ElnnGYmYNCKSHLGVPJHlyf2DNECDX8QDqvigUCBzO4ULmpnxr4GiZZqdQyeH1BgU0Ag"; + String encoded = "Basic aGVsbG86d29ybGQ="; when(request.getHeader(AUTHORIZATION)).thenReturn(encoded); // expect @@ -68,9 +59,6 @@ void setUp() { @Test void 요청_헤더에_인증_정보가_없을_경우_예외를_발생시킨다() { // given - AuthExtractor authExtractor = new AuthExtractor( - new AuthTokenManager(new JwtTokenProvider(TEST_SECRET_KEY), ACCESS_TOKEN_EXPIRE_TIME) - ); HttpServletRequest request = mock(HttpServletRequest.class); when(request.getHeader(AUTHORIZATION)).thenReturn(null); @@ -83,7 +71,6 @@ void setUp() { @Test void 헤더의_담긴_토큰이_유효하지_않으면_예외를_발생시킨다() { // given - AuthExtractor authExtractor = new AuthExtractor(authTokenManager); HttpServletRequest request = mock(HttpServletRequest.class); String notEncoded = "Bearer wrong.long.token"; when(request.getHeader(AUTHORIZATION)).thenReturn(notEncoded); diff --git a/backend/src/test/java/dev/tripdraw/auth/application/AuthFacadeServiceTest.java b/backend/src/test/java/dev/tripdraw/auth/application/AuthFacadeServiceTest.java new file mode 100644 index 000000000..92d3f535c --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/auth/application/AuthFacadeServiceTest.java @@ -0,0 +1,120 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.BDDMockito.given; + +import dev.tripdraw.auth.domain.RefreshToken; +import dev.tripdraw.auth.domain.RefreshTokenRepository; +import dev.tripdraw.auth.dto.OauthRequest; +import dev.tripdraw.auth.dto.OauthResponse; +import dev.tripdraw.auth.dto.RegisterRequest; +import dev.tripdraw.auth.dto.TokenRefreshRequest; +import dev.tripdraw.auth.oauth.OauthClientProvider; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.test.TestKakaoApiClient; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@Transactional +@SpringBootTest +class AuthFacadeServiceTest { + + @Autowired + private AuthFacadeService authFacadeService; + + @MockBean + private OauthClientProvider oauthClientProvider; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private RefreshTokenRepository refreshTokenRepository; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @BeforeEach + void setUp() { + given(oauthClientProvider.provide(KAKAO)).willReturn(new TestKakaoApiClient()); + } + + @Test + void 가입된_회원이_카카오_소셜_로그인하면_토큰이_포함된_응답을_반환한다() { + // given + memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + OauthRequest oauthRequest = new OauthRequest(KAKAO, "oauth.kakao.token"); + + // when + OauthResponse response = authFacadeService.login(oauthRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(response.accessToken()).isNotEmpty(); + softly.assertThat(response.refreshToken()).isNotEmpty(); + }); + } + + @Test + void 신규_회원이_로그인하면_회원을_저장하고_빈_토큰이_포함된_응답을_반환한다() { + // given + OauthRequest oauthRequest = new OauthRequest(KAKAO, "oauth.kakao.token"); + + // when + OauthResponse response = authFacadeService.login(oauthRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(response.accessToken()).isEmpty(); + softly.assertThat(response.refreshToken()).isEmpty(); + }); + } + + @Test + void 신규_회원의_닉네임을_등록하면_토큰이_포함된_응답을_반환한다() { + // given + Member member = Member.of("kakaoId", KAKAO); + memberRepository.save(member); + + RegisterRequest registerRequest = new RegisterRequest("통후추", KAKAO, "oauth.kakao.token"); + + // when + OauthResponse response = authFacadeService.register(registerRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(response.accessToken()).isNotEmpty(); + softly.assertThat(response.refreshToken()).isNotEmpty(); + }); + } + + @Test + void Refresh_토큰을_입력받아_Access_토큰과_Refresh_토큰을_재발급한다() { + // given + Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + String refreshToken = jwtTokenProvider.generateRefreshToken(); + refreshTokenRepository.save(new RefreshToken(member.id(), refreshToken)); + TokenRefreshRequest tokenRefreshRequest = new TokenRefreshRequest(refreshToken); + + // when + OauthResponse response = authFacadeService.refresh(tokenRefreshRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(response.accessToken()).isNotEmpty(); + softly.assertThat(response.refreshToken()).isNotEmpty(); + softly.assertThat(refreshTokenRepository.findByToken(refreshToken)).isEmpty(); + softly.assertThat(refreshTokenRepository.findByToken(response.refreshToken())).isPresent(); + }); + } +} diff --git a/backend/src/test/java/dev/tripdraw/auth/application/AuthServiceTest.java b/backend/src/test/java/dev/tripdraw/auth/application/AuthServiceTest.java new file mode 100644 index 000000000..27eefe303 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/auth/application/AuthServiceTest.java @@ -0,0 +1,108 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.member.exception.MemberExceptionType.DUPLICATE_NICKNAME; +import static dev.tripdraw.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; +import static dev.tripdraw.test.fixture.MemberFixture.사용자; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.common.auth.OauthType; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.exception.MemberException; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@ExtendWith(MockitoExtension.class) +class AuthServiceTest { + + @InjectMocks + private AuthService authService; + + @Mock + private MemberRepository memberRepository; + + private final OauthInfo oauthInfo = new OauthInfo("id", KAKAO); + + @Test + void 신규_회원이_로그인하면_회원을_저장_후_빈_회원을_반환한다() { + // given + given(memberRepository.findByOauthIdAndOauthType(any(String.class), any(OauthType.class))) + .willReturn(Optional.empty()); + + // when + Optional member = authService.login(oauthInfo); + + // then + assertSoftly(softly -> { + softly.assertThat(member).isNotPresent(); + verify(memberRepository, times(1)).save(any(Member.class)); + }); + } + + @Test + void 기존의_회원이_로그인하면_회원_정보를_반환한다() { + // given + Member 사용자 = 사용자(); + given(memberRepository.findByOauthIdAndOauthType(any(String.class), any(OauthType.class))) + .willReturn(Optional.of(사용자)); + + // when + Optional member = authService.login(oauthInfo); + + // then + assertThat(member).isPresent(); + } + + @Test + void 신규_회원의_닉네임을_등록_후_회원_정보를_반환한다() { + // given + given(memberRepository.findByOauthIdAndOauthType(any(String.class), any(OauthType.class))) + .willReturn(Optional.of(사용자())); + + // when + Optional member = authService.register(oauthInfo, "통후추"); + + // then + assertThat(member).isPresent(); + } + + @Test + void 신규_회원의_닉네임을_등록할_때_회원이_존재하지_않으면_예외가_발생한다() { + // given + given(memberRepository.findByOauthIdAndOauthType(any(String.class), any(OauthType.class))) + .willReturn(Optional.empty()); + + // expect + assertThatThrownBy(() -> authService.register(oauthInfo, "통후추")) + .isInstanceOf(MemberException.class) + .hasMessage(MEMBER_NOT_FOUND.message()); + } + + @Test + void 신규_회원의_닉네임을_등록할_때_이미_존재하는_닉네임이라면_예외가_발생한다() { + // given + given(memberRepository.existsByNickname(any(String.class))) + .willReturn(true); + + // expect + assertThatThrownBy(() -> authService.register(oauthInfo, "통후추")) + .isInstanceOf(MemberException.class) + .hasMessage(DUPLICATE_NICKNAME.message()); + } +} diff --git a/backend/src/test/java/dev/tripdraw/auth/application/JwtTokenProviderTest.java b/backend/src/test/java/dev/tripdraw/auth/application/JwtTokenProviderTest.java new file mode 100644 index 000000000..3a1c4e995 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/auth/application/JwtTokenProviderTest.java @@ -0,0 +1,112 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.auth.exception.AuthExceptionType.EXPIRED_ACCESS_TOKEN; +import static dev.tripdraw.auth.exception.AuthExceptionType.EXPIRED_REFRESH_TOKEN; +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_TOKEN; +import static dev.tripdraw.test.fixture.AuthFixture.만료된_토큰_생성용_ACCESS_TOKEN_설정; +import static dev.tripdraw.test.fixture.AuthFixture.만료된_토큰_생성용_REFRESH_TOKEN_설정; +import static dev.tripdraw.test.fixture.AuthFixture.테스트_ACCESS_TOKEN_설정; +import static dev.tripdraw.test.fixture.AuthFixture.테스트_REFRESH_TOKEN_설정; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import dev.tripdraw.auth.exception.AuthException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class JwtTokenProviderTest { + + private final JwtTokenProvider jwtTokenProvider = new JwtTokenProvider( + 테스트_ACCESS_TOKEN_설정(), + 테스트_REFRESH_TOKEN_설정() + ); + + @Test + void ACCESS_토큰_정보를_추출한다() { + // given + String accessToken = jwtTokenProvider.generateAccessToken("memberId"); + + // when + String subject = jwtTokenProvider.extractAccessToken(accessToken); + + // then + assertThat(subject).isEqualTo("memberId"); + } + + @Test + void 대상을_입력_받아_ACCESS_토큰을_생성한다() { + // given + String subject = "memberId"; + + // when + String accessToken = jwtTokenProvider.generateAccessToken(subject); + + // then + assertThat(accessToken).isNotEmpty(); + } + + @Test + void 유효하지_않은_ACCESS_토큰의_정보를_추출할_때_예외를_발생시킨다() { + // given + String invalidToken = "Invalid.Token.XD"; + + // expect + assertThatThrownBy(() -> jwtTokenProvider.extractAccessToken(invalidToken)) + .isInstanceOf(AuthException.class) + .hasMessage(INVALID_TOKEN.message()); + } + + @Test + void 만료된_ACCESS_토큰의_정보를_추출할_때_예외를_발생시킨다() { + // given + JwtTokenProvider expiredTokenProvider = new JwtTokenProvider( + 만료된_토큰_생성용_ACCESS_TOKEN_설정(), + 만료된_토큰_생성용_REFRESH_TOKEN_설정() + ); + String expiredToken = expiredTokenProvider.generateAccessToken("memberId"); + + // expect + assertThatThrownBy(() -> jwtTokenProvider.extractAccessToken(expiredToken)) + .isInstanceOf(AuthException.class) + .hasMessage(EXPIRED_ACCESS_TOKEN.message()); + } + + @Test + void REFRESH_토큰을_생성한다() { + // when + String refreshToken = jwtTokenProvider.generateRefreshToken(); + + // then + assertThatNoException().isThrownBy(() -> jwtTokenProvider.validateRefreshToken(refreshToken)); + } + + @Test + void 유효하지_않은_REFRESH_TOKEN인_경우_예외를_발생시킨다() { + // given + String invalidToken = "Invalid.Token.XD"; + + // expect + assertThatThrownBy(() -> jwtTokenProvider.validateRefreshToken(invalidToken)) + .isInstanceOf(AuthException.class) + .hasMessage(INVALID_TOKEN.message()); + } + + @Test + void 만료된_REFRESH_토큰인_경우_예외를_발생시킨다() { + // given + JwtTokenProvider expiredTokenProvider = new JwtTokenProvider( + 만료된_토큰_생성용_ACCESS_TOKEN_설정(), + 만료된_토큰_생성용_REFRESH_TOKEN_설정() + ); + String expiredToken = expiredTokenProvider.generateRefreshToken(); + + // expect + assertThatThrownBy(() -> jwtTokenProvider.validateRefreshToken(expiredToken)) + .isInstanceOf(AuthException.class) + .hasMessage(EXPIRED_REFRESH_TOKEN.message()); + } +} diff --git a/backend/src/test/java/dev/tripdraw/auth/application/OAuthServiceTest.java b/backend/src/test/java/dev/tripdraw/auth/application/OAuthServiceTest.java new file mode 100644 index 000000000..b37f56102 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/auth/application/OAuthServiceTest.java @@ -0,0 +1,49 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.Mockito.when; + +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.auth.oauth.OauthClientProvider; +import dev.tripdraw.test.TestKakaoApiClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@ExtendWith(MockitoExtension.class) +class OAuthServiceTest { + + @InjectMocks + private OAuthService oAuthService; + + @Mock + private OauthClientProvider oauthClientProvider; + + @BeforeEach + void setUp() { + when(oauthClientProvider.provide(KAKAO)).thenReturn(new TestKakaoApiClient()); + } + + @Test + void Oauth_공통_정보를_반환한다() { + // given + String oauthToken = "oauth.kakao.token"; + + // when + OauthInfo oauthInfo = oAuthService.request(KAKAO, oauthToken); + + // then + assertSoftly(softly -> { + softly.assertThat(oauthInfo.oauthId()).isEqualTo("kakaoId"); + softly.assertThat(oauthInfo.oauthType()).isEqualTo(KAKAO); + }); + } +} diff --git a/backend/src/test/java/dev/tripdraw/auth/application/TokenGenerateServiceTest.java b/backend/src/test/java/dev/tripdraw/auth/application/TokenGenerateServiceTest.java new file mode 100644 index 000000000..a0dc881bc --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/auth/application/TokenGenerateServiceTest.java @@ -0,0 +1,99 @@ +package dev.tripdraw.auth.application; + +import static dev.tripdraw.auth.exception.AuthExceptionType.INVALID_TOKEN; +import static dev.tripdraw.test.fixture.AuthFixture.테스트_ACCESS_TOKEN_설정; +import static dev.tripdraw.test.fixture.AuthFixture.테스트_REFRESH_TOKEN_설정; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import dev.tripdraw.auth.domain.RefreshToken; +import dev.tripdraw.auth.domain.RefreshTokenRepository; +import dev.tripdraw.auth.dto.OauthResponse; +import dev.tripdraw.auth.exception.AuthException; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@ExtendWith(MockitoExtension.class) +class TokenGenerateServiceTest { + + @InjectMocks + private TokenGenerateService tokenGenerateService; + + @Spy + private JwtTokenProvider jwtTokenProvider = new JwtTokenProvider( + 테스트_ACCESS_TOKEN_설정(), + 테스트_REFRESH_TOKEN_설정() + ); + + @Mock + private RefreshTokenRepository refreshTokenRepository; + + @Test + void refreshToken을_발급하여_저장하고_Access_토큰과_Refresh_토큰을_반환한다() { + // given + Long memberId = 1L; + + // when + OauthResponse result = tokenGenerateService.generate(memberId); + + // then + assertSoftly(softly -> { + softly.assertThat(result.accessToken()).isNotEmpty(); + softly.assertThat(result.refreshToken()).isNotEmpty(); + verify(refreshTokenRepository, times(1)).save(any(RefreshToken.class)); + }); + } + + @Test + void Refresh_토큰_재발급시_입력받은_Refresh_토큰이_존재하지_않는_토큰인_경우_예외가_발생한다() { + // given + given(refreshTokenRepository.findByToken(any(String.class))).willReturn(Optional.empty()); + String token = jwtTokenProvider.generateRefreshToken(); + + // expect + assertThatThrownBy(() -> tokenGenerateService.refresh(token)) + .isInstanceOf(AuthException.class) + .hasMessage(INVALID_TOKEN.message()); + } + + @Test + void Refresh_토큰을_입력받아_Access_토큰과_Refresh_토큰을_재발급한다() { + // given + String refreshToken = jwtTokenProvider.generateRefreshToken(); + given(refreshTokenRepository.findByToken(any(String.class))) + .willReturn(Optional.of(new RefreshToken(1L, 1L, refreshToken))); + + // when + OauthResponse result = tokenGenerateService.refresh(refreshToken); + + // then + assertSoftly(softly -> { + softly.assertThat(result.accessToken()).isNotEmpty(); + softly.assertThat(result.refreshToken()).isNotEmpty(); + softly.assertThat(result.refreshToken()).isNotEqualTo(refreshToken); + }); + } + + @Test + void 빈_토큰을_발급한다() { + // given + OauthResponse emptyToken = new OauthResponse("", ""); + + // expect + assertThat(tokenGenerateService.generateEmptyToken()).isEqualTo(emptyToken); + } +} diff --git a/backend/src/test/java/dev/tripdraw/auth/domain/RefreshTokenRepositoryTest.java b/backend/src/test/java/dev/tripdraw/auth/domain/RefreshTokenRepositoryTest.java new file mode 100644 index 000000000..71b40a828 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/auth/domain/RefreshTokenRepositoryTest.java @@ -0,0 +1,51 @@ +package dev.tripdraw.auth.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.tripdraw.common.config.JpaConfig; +import dev.tripdraw.common.config.QueryDslConfig; +import java.util.Optional; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@DataJpaTest +@Import({JpaConfig.class, QueryDslConfig.class}) +class RefreshTokenRepositoryTest { + + @Autowired + private RefreshTokenRepository refreshTokenRepository; + + @Test + void 토큰을_입력받아_refreshToken_객체를_반환한다() { + // given + long memberId = 1L; + RefreshToken refreshToken = new RefreshToken(memberId, "refreshToken"); + refreshTokenRepository.save(refreshToken); + + // when + Optional findToken = refreshTokenRepository.findByToken(refreshToken.token()); + + // then + assertThat(findToken).isPresent(); + } + + @Test + void 사용자_아이디를_입력받아_해당되는_모든_RefreshToken을_제거한다() { + // given + long memberId = 1L; + refreshTokenRepository.save(new RefreshToken(memberId, "refreshToken")); + refreshTokenRepository.save(new RefreshToken(memberId, "refreshToken")); + + // when + refreshTokenRepository.deleteByMemberId(memberId); + + // then + assertThat(refreshTokenRepository.count()).isZero(); + } +} diff --git a/backend/src/test/java/dev/tripdraw/dto/auth/KakaoInfoResponseTest.java b/backend/src/test/java/dev/tripdraw/auth/dto/KakaoInfoResponseTest.java similarity index 89% rename from backend/src/test/java/dev/tripdraw/dto/auth/KakaoInfoResponseTest.java rename to backend/src/test/java/dev/tripdraw/auth/dto/KakaoInfoResponseTest.java index 9716ee778..75900eec8 100644 --- a/backend/src/test/java/dev/tripdraw/dto/auth/KakaoInfoResponseTest.java +++ b/backend/src/test/java/dev/tripdraw/auth/dto/KakaoInfoResponseTest.java @@ -1,6 +1,6 @@ -package dev.tripdraw.dto.auth; +package dev.tripdraw.auth.dto; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; import static org.assertj.core.api.SoftAssertions.assertSoftly; import java.time.LocalDateTime; diff --git a/backend/src/test/java/dev/tripdraw/domain/oauth/KakaoApiClientTest.java b/backend/src/test/java/dev/tripdraw/auth/oauth/KakaoApiClientTest.java similarity index 88% rename from backend/src/test/java/dev/tripdraw/domain/oauth/KakaoApiClientTest.java rename to backend/src/test/java/dev/tripdraw/auth/oauth/KakaoApiClientTest.java index 4bd407c18..b33dd79bf 100644 --- a/backend/src/test/java/dev/tripdraw/domain/oauth/KakaoApiClientTest.java +++ b/backend/src/test/java/dev/tripdraw/auth/oauth/KakaoApiClientTest.java @@ -1,14 +1,14 @@ -package dev.tripdraw.domain.oauth; +package dev.tripdraw.auth.oauth; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import dev.tripdraw.application.oauth.KakaoApiClient; -import dev.tripdraw.dto.auth.KakaoInfoResponse; -import dev.tripdraw.dto.auth.OauthInfo; +import dev.tripdraw.auth.dto.KakaoInfoResponse; +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.common.auth.OauthType; import java.time.LocalDateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; diff --git a/backend/src/test/java/dev/tripdraw/domain/oauth/OauthClientProviderTest.java b/backend/src/test/java/dev/tripdraw/auth/oauth/OauthClientProviderTest.java similarity index 78% rename from backend/src/test/java/dev/tripdraw/domain/oauth/OauthClientProviderTest.java rename to backend/src/test/java/dev/tripdraw/auth/oauth/OauthClientProviderTest.java index 55eb3f031..3f325536b 100644 --- a/backend/src/test/java/dev/tripdraw/domain/oauth/OauthClientProviderTest.java +++ b/backend/src/test/java/dev/tripdraw/auth/oauth/OauthClientProviderTest.java @@ -1,11 +1,8 @@ -package dev.tripdraw.domain.oauth; +package dev.tripdraw.auth.oauth; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; import static org.assertj.core.api.Assertions.assertThat; -import dev.tripdraw.application.oauth.KakaoApiClient; -import dev.tripdraw.application.oauth.OauthClient; -import dev.tripdraw.application.oauth.OauthClientProvider; import java.util.Set; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; diff --git a/backend/src/test/java/dev/tripdraw/presentation/controller/AuthControllerTest.java b/backend/src/test/java/dev/tripdraw/auth/presentation/AuthControllerTest.java similarity index 61% rename from backend/src/test/java/dev/tripdraw/presentation/controller/AuthControllerTest.java rename to backend/src/test/java/dev/tripdraw/auth/presentation/AuthControllerTest.java index 0f4de118e..8e697da22 100644 --- a/backend/src/test/java/dev/tripdraw/presentation/controller/AuthControllerTest.java +++ b/backend/src/test/java/dev/tripdraw/auth/presentation/AuthControllerTest.java @@ -1,21 +1,29 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.auth.presentation; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.test.fixture.AuthFixture.만료된_토큰_생성용_ACCESS_TOKEN_설정; +import static dev.tripdraw.test.fixture.AuthFixture.만료된_토큰_생성용_REFRESH_TOKEN_설정; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.Mockito.when; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import dev.tripdraw.application.oauth.OauthClientProvider; -import dev.tripdraw.common.TestKakaoApiClient; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.dto.auth.OauthRequest; -import dev.tripdraw.dto.auth.OauthResponse; -import dev.tripdraw.dto.auth.RegisterRequest; +import dev.tripdraw.auth.application.JwtTokenProvider; +import dev.tripdraw.auth.domain.RefreshToken; +import dev.tripdraw.auth.domain.RefreshTokenRepository; +import dev.tripdraw.auth.dto.OauthRequest; +import dev.tripdraw.auth.dto.OauthResponse; +import dev.tripdraw.auth.dto.RegisterRequest; +import dev.tripdraw.auth.dto.TokenRefreshRequest; +import dev.tripdraw.auth.oauth.OauthClientProvider; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.test.ControllerTest; +import dev.tripdraw.test.TestKakaoApiClient; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; @@ -33,18 +41,23 @@ class AuthControllerTest extends ControllerTest { @LocalServerPort - int port; + private int port; @MockBean - OauthClientProvider oauthClientProvider; + private OauthClientProvider oauthClientProvider; @Autowired - MemberRepository memberRepository; + private MemberRepository memberRepository; + + @Autowired + private RefreshTokenRepository refreshTokenRepository; + + @Autowired + private JwtTokenProvider jwtTokenProvider; @BeforeEach - void setUp() { + public void setUp() { RestAssured.port = port; - when(oauthClientProvider.provide(KAKAO)) .thenReturn(new TestKakaoApiClient()); } @@ -72,6 +85,7 @@ class 로그인할_때 { assertSoftly(softly -> { softly.assertThat(response.statusCode()).isEqualTo(OK.value()); softly.assertThat(oauthResponse.accessToken()).isNotNull(); + softly.assertThat(oauthResponse.refreshToken()).isNotNull(); }); } @@ -94,6 +108,7 @@ class 로그인할_때 { assertSoftly(softly -> { softly.assertThat(response.statusCode()).isEqualTo(OK.value()); softly.assertThat(oauthResponse.accessToken()).isEmpty(); + softly.assertThat(oauthResponse.refreshToken()).isEmpty(); }); } } @@ -123,6 +138,7 @@ class 신규_회원의_닉네임을_등록할_때 { assertSoftly(softly -> { softly.assertThat(response.statusCode()).isEqualTo(OK.value()); softly.assertThat(oauthResponse.accessToken()).isNotEmpty(); + softly.assertThat(oauthResponse.refreshToken()).isNotEmpty(); }); } @@ -169,4 +185,54 @@ class 신규_회원의_닉네임을_등록할_때 { .statusCode(BAD_REQUEST.value()); } } + + @Nested + class Refresh_토큰_재발급_시 { + + @Test + void 만료기간이_남은_Refresh_토큰이면_Access_토큰과_Refresh_토큰을_재발급한다() { + // given + Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + String refreshToken = jwtTokenProvider.generateRefreshToken(); + refreshTokenRepository.save(new RefreshToken(member.id(), refreshToken)); + TokenRefreshRequest tokenRefreshRequest = new TokenRefreshRequest(refreshToken); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .contentType(APPLICATION_JSON_VALUE) + .body(tokenRefreshRequest) + .when().post("/oauth/refresh") + .then().log().all() + .extract(); + + // then + OauthResponse oauthResponse = response.as(OauthResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(oauthResponse.accessToken()).isNotEmpty(); + softly.assertThat(oauthResponse.refreshToken()).isNotEmpty(); + }); + } + + @Test + void 만료기간이_지난_Refresh_토큰이면_401_예외가_발생한다() { + // given + Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + JwtTokenProvider expiredTokenProvider = new JwtTokenProvider( + 만료된_토큰_생성용_ACCESS_TOKEN_설정(), + 만료된_토큰_생성용_REFRESH_TOKEN_설정() + ); + String expiredToken = expiredTokenProvider.generateRefreshToken(); + refreshTokenRepository.save(new RefreshToken(member.id(), expiredToken)); + TokenRefreshRequest tokenRefreshRequest = new TokenRefreshRequest(expiredToken); + + RestAssured.given().log().all() + .contentType(APPLICATION_JSON_VALUE) + .body(tokenRefreshRequest) + .when().post("/oauth/refresh") + .then().log().all() + .statusCode(UNAUTHORIZED.value()); + } + } } diff --git a/backend/src/test/java/dev/tripdraw/common/domain/TripPagingTest.java b/backend/src/test/java/dev/tripdraw/common/domain/TripPagingTest.java new file mode 100644 index 000000000..d02967ce2 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/common/domain/TripPagingTest.java @@ -0,0 +1,40 @@ +package dev.tripdraw.common.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.tripdraw.trip.query.TripPaging; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class TripPagingTest { + + @ParameterizedTest + @CsvSource({"19, false", "20, false", "21, true"}) + void 다음_페이지가_있는지_확인한다(int size, boolean expected) { + // given + TripPaging tripPaging = new TripPaging(1L, 20); + + // when + boolean actual = tripPaging.hasNextPage(size); + + // then + assertThat(actual).isEqualTo(expected); + } + + @Test + void limit이_100을_넘기면_100으로_조정된다() { + // given + int limit = 101; + + // when + TripPaging tripPaging = new TripPaging(1L, limit); + + // then + assertThat(tripPaging.limit()).isEqualTo(100); + } +} diff --git a/backend/src/test/java/dev/tripdraw/common/QueryCounterTest.java b/backend/src/test/java/dev/tripdraw/common/log/QueryCounterTest.java similarity index 97% rename from backend/src/test/java/dev/tripdraw/common/QueryCounterTest.java rename to backend/src/test/java/dev/tripdraw/common/log/QueryCounterTest.java index 2d15ea388..f2411eafc 100644 --- a/backend/src/test/java/dev/tripdraw/common/QueryCounterTest.java +++ b/backend/src/test/java/dev/tripdraw/common/log/QueryCounterTest.java @@ -1,4 +1,4 @@ -package dev.tripdraw.common; +package dev.tripdraw.common.log; import static org.assertj.core.api.Assertions.assertThat; @@ -11,7 +11,7 @@ @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class QueryCounterTest { - + @Test void 쿼리_개수를_하나_증가시킨다() { // given diff --git a/backend/src/test/java/dev/tripdraw/domain/trip/RouteTest.java b/backend/src/test/java/dev/tripdraw/domain/trip/RouteTest.java deleted file mode 100644 index ba252bc65..000000000 --- a/backend/src/test/java/dev/tripdraw/domain/trip/RouteTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package dev.tripdraw.domain.trip; - -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_DELETED; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_IN_TRIP; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import dev.tripdraw.exception.trip.TripException; -import java.time.LocalDateTime; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(ReplaceUnderscores.class) -class RouteTest { - - @Test - void 경로에_좌표를_추가한다() { - // given - Route route = new Route(); - Point point = new Point(1.1, 2.2, LocalDateTime.now()); - - // when - route.add(point); - - // then - assertThat(route.points()).hasSize(1); - } - - @Test - void 경로에서_위치정보를_삭제한다() { - // given - Route route = new Route(); - Point point = new Point(1L, 1.1, 2.2, false, LocalDateTime.now()); - route.add(point); - Long id = point.id(); - - // when - route.deletePointById(id); - - // then - assertThat(point.isDeleted()).isTrue(); - } - - @Test - void 경로에_존재하지_않는_위치정보를_삭제하면_예외를_발생시킨다() { - // given - Route route = new Route(); - Point point = new Point(1L, 1.1, 2.2, false, LocalDateTime.now()); - Point inexistentPoint = new Point(2L, 1.1, 2.2, false, LocalDateTime.now()); - - route.add(point); - - // expect - assertThatThrownBy(() -> route.deletePointById(inexistentPoint.id())) - .isInstanceOf(TripException.class) - .hasMessage(POINT_NOT_IN_TRIP.message()); - } - - @Test - void 이미_삭제된_위치정보를_삭제하면_예외를_발생시킨다() { - // given - Route route = new Route(); - Point point = new Point(1L, 1.1, 2.2, false, LocalDateTime.now()); - - route.add(point); - route.deletePointById(point.id()); - - // expect - assertThatThrownBy(() -> route.deletePointById(point.id())) - .isInstanceOf(TripException.class) - .hasMessage(POINT_ALREADY_DELETED.message()); - } - - @Test - void 위치를_ID로_조회한다() { - // given - Route route = new Route(); - Point point1 = new Point(1L, 1.1, 2.1, false, LocalDateTime.now()); - Point point2 = new Point(2L, 1.2, 2.2, false, LocalDateTime.now()); - route.add(point1); - route.add(point2); - - // when - Point foundPoint = route.findPointById(1L); - - // then - assertThat(foundPoint).isEqualTo(point1); - } - - @Test - void 위치를_존재하지_않는_ID로_조회하면_예외가_발생한다() { - // given - Route route = new Route(); - Point point = new Point(1L, 1.1, 2.1, false, LocalDateTime.now()); - route.add(point); - - // expect - assertThatThrownBy(() -> route.findPointById(2L)) - .isInstanceOf(TripException.class) - .hasMessage(POINT_NOT_FOUND.message()); - } -} - diff --git a/backend/src/test/java/dev/tripdraw/application/draw/PostCreateEventHandlerIntegrationTest.java b/backend/src/test/java/dev/tripdraw/draw/application/PostCreateEventHandlerIntegrationTest.java similarity index 80% rename from backend/src/test/java/dev/tripdraw/application/draw/PostCreateEventHandlerIntegrationTest.java rename to backend/src/test/java/dev/tripdraw/draw/application/PostCreateEventHandlerIntegrationTest.java index 6757d4db5..e33f0391b 100644 --- a/backend/src/test/java/dev/tripdraw/application/draw/PostCreateEventHandlerIntegrationTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/application/PostCreateEventHandlerIntegrationTest.java @@ -1,15 +1,15 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; -import static dev.tripdraw.test.TestFixture.감상; -import static dev.tripdraw.test.TestFixture.여행; +import static dev.tripdraw.test.fixture.TestFixture.감상; +import static dev.tripdraw.test.fixture.TestFixture.여행; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.timeout; -import dev.tripdraw.domain.post.PostCreateEvent; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.TripRepository; +import dev.tripdraw.post.domain.PostCreateEvent; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.trip.domain.TripRepository; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SpringBootTest -public class PostCreateEventHandlerIntegrationTest { +class PostCreateEventHandlerIntegrationTest { @MockBean private RouteImageGenerator routeImageGenerator; @@ -45,7 +45,7 @@ public class PostCreateEventHandlerIntegrationTest { PostCreateEvent postCreateEvent = new PostCreateEvent(1L, 1L); given(tripRepository.getTripWithPoints(postCreateEvent.tripId())) .willReturn(여행()); - given(postRepository.getById(postCreateEvent.postId())) + given(postRepository.getByPostId(postCreateEvent.postId())) .willReturn(감상()); // when diff --git a/backend/src/test/java/dev/tripdraw/application/draw/PostCreateEventHandlerTest.java b/backend/src/test/java/dev/tripdraw/draw/application/PostCreateEventHandlerTest.java similarity index 79% rename from backend/src/test/java/dev/tripdraw/application/draw/PostCreateEventHandlerTest.java rename to backend/src/test/java/dev/tripdraw/draw/application/PostCreateEventHandlerTest.java index ed538f9bc..d9323942c 100644 --- a/backend/src/test/java/dev/tripdraw/application/draw/PostCreateEventHandlerTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/application/PostCreateEventHandlerTest.java @@ -1,15 +1,15 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; -import static dev.tripdraw.test.TestFixture.감상; -import static dev.tripdraw.test.TestFixture.여행; +import static dev.tripdraw.test.fixture.TestFixture.감상; +import static dev.tripdraw.test.fixture.TestFixture.여행; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; -import dev.tripdraw.domain.post.PostCreateEvent; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.TripRepository; +import dev.tripdraw.post.domain.PostCreateEvent; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.trip.domain.TripRepository; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -41,7 +41,7 @@ class PostCreateEventHandlerTest { PostCreateEvent postCreateEvent = new PostCreateEvent(1L, 1L); given(tripRepository.getTripWithPoints(postCreateEvent.tripId())) .willReturn(여행()); - given(postRepository.getById(postCreateEvent.postId())) + given(postRepository.getByPostId(postCreateEvent.postId())) .willReturn(감상()); // when diff --git a/backend/src/test/java/dev/tripdraw/application/draw/RouteImageGeneratorTest.java b/backend/src/test/java/dev/tripdraw/draw/application/RouteImageGeneratorTest.java similarity index 91% rename from backend/src/test/java/dev/tripdraw/application/draw/RouteImageGeneratorTest.java rename to backend/src/test/java/dev/tripdraw/draw/application/RouteImageGeneratorTest.java index 559315b7c..a9fddf78b 100644 --- a/backend/src/test/java/dev/tripdraw/application/draw/RouteImageGeneratorTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/application/RouteImageGeneratorTest.java @@ -1,10 +1,12 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import dev.tripdraw.draw.application.RouteImageGenerator; +import dev.tripdraw.draw.application.RouteImageUploader; import java.awt.image.BufferedImage; import java.util.List; import org.junit.jupiter.api.DisplayNameGeneration; diff --git a/backend/src/test/java/dev/tripdraw/application/draw/RouteImageUploaderTest.java b/backend/src/test/java/dev/tripdraw/draw/application/RouteImageUploaderTest.java similarity index 95% rename from backend/src/test/java/dev/tripdraw/application/draw/RouteImageUploaderTest.java rename to backend/src/test/java/dev/tripdraw/draw/application/RouteImageUploaderTest.java index 4886cdf86..109eab90b 100644 --- a/backend/src/test/java/dev/tripdraw/application/draw/RouteImageUploaderTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/application/RouteImageUploaderTest.java @@ -1,9 +1,10 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; +import dev.tripdraw.draw.application.RouteImageUploader; import java.awt.image.BufferedImage; import java.io.File; import javax.imageio.ImageIO; @@ -29,7 +30,7 @@ class RouteImageUploaderTest { // expect try (MockedStatic imageIO = Mockito.mockStatic(ImageIO.class)) { String imageUrl = routeImageUploader.upload(bufferedImage); - + imageIO.verify( () -> ImageIO.write(any(BufferedImage.class), any(String.class), any(File.class)), times(1) diff --git a/backend/src/test/java/dev/tripdraw/application/draw/TripUpdateEventHandlerIntegrationTest.java b/backend/src/test/java/dev/tripdraw/draw/application/TripUpdateEventHandlerIntegrationTest.java similarity index 89% rename from backend/src/test/java/dev/tripdraw/application/draw/TripUpdateEventHandlerIntegrationTest.java rename to backend/src/test/java/dev/tripdraw/draw/application/TripUpdateEventHandlerIntegrationTest.java index 654db3b93..78523cbe2 100644 --- a/backend/src/test/java/dev/tripdraw/application/draw/TripUpdateEventHandlerIntegrationTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/application/TripUpdateEventHandlerIntegrationTest.java @@ -1,13 +1,13 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; -import static dev.tripdraw.test.TestFixture.여행; +import static dev.tripdraw.test.fixture.TestFixture.여행; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.timeout; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.domain.trip.TripUpdateEvent; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.domain.TripUpdateEvent; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/dev/tripdraw/application/draw/TripUpdateEventHandlerTest.java b/backend/src/test/java/dev/tripdraw/draw/application/TripUpdateEventHandlerTest.java similarity index 87% rename from backend/src/test/java/dev/tripdraw/application/draw/TripUpdateEventHandlerTest.java rename to backend/src/test/java/dev/tripdraw/draw/application/TripUpdateEventHandlerTest.java index e0d6e6c0e..45bdc12c8 100644 --- a/backend/src/test/java/dev/tripdraw/application/draw/TripUpdateEventHandlerTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/application/TripUpdateEventHandlerTest.java @@ -1,13 +1,13 @@ -package dev.tripdraw.application.draw; +package dev.tripdraw.draw.application; -import static dev.tripdraw.test.TestFixture.여행; +import static dev.tripdraw.test.fixture.TestFixture.여행; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.domain.trip.TripUpdateEvent; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.domain.TripUpdateEvent; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -26,7 +26,7 @@ class TripUpdateEventHandlerTest { @Mock private TripRepository tripRepository; - + @InjectMocks private TripUpdateEventHandler tripUpdateEventHandler; diff --git a/backend/src/test/java/dev/tripdraw/domain/draw/CoordinatesTest.java b/backend/src/test/java/dev/tripdraw/draw/domain/CoordinatesTest.java similarity index 92% rename from backend/src/test/java/dev/tripdraw/domain/draw/CoordinatesTest.java rename to backend/src/test/java/dev/tripdraw/draw/domain/CoordinatesTest.java index e0e2bbe99..c738758dc 100644 --- a/backend/src/test/java/dev/tripdraw/domain/draw/CoordinatesTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/domain/CoordinatesTest.java @@ -1,11 +1,14 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; -import static dev.tripdraw.exception.draw.DrawExceptionType.INVALID_COORDINATES; +import static dev.tripdraw.draw.exception.DrawExceptionType.INVALID_COORDINATES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import dev.tripdraw.exception.draw.DrawException; +import dev.tripdraw.draw.domain.Coordinates; +import dev.tripdraw.draw.domain.Position; +import dev.tripdraw.draw.domain.Positions; +import dev.tripdraw.draw.exception.DrawException; import java.util.List; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; diff --git a/backend/src/test/java/dev/tripdraw/domain/draw/PositionsTest.java b/backend/src/test/java/dev/tripdraw/draw/domain/PositionsTest.java similarity index 95% rename from backend/src/test/java/dev/tripdraw/domain/draw/PositionsTest.java rename to backend/src/test/java/dev/tripdraw/draw/domain/PositionsTest.java index 78530b470..1feb7ce7a 100644 --- a/backend/src/test/java/dev/tripdraw/domain/draw/PositionsTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/domain/PositionsTest.java @@ -1,7 +1,9 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; import static org.assertj.core.api.Assertions.assertThat; +import dev.tripdraw.draw.domain.Position; +import dev.tripdraw.draw.domain.Positions; import java.util.List; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; diff --git a/backend/src/test/java/dev/tripdraw/domain/draw/RouteImageDrawerTest.java b/backend/src/test/java/dev/tripdraw/draw/domain/RouteImageDrawerTest.java similarity index 94% rename from backend/src/test/java/dev/tripdraw/domain/draw/RouteImageDrawerTest.java rename to backend/src/test/java/dev/tripdraw/draw/domain/RouteImageDrawerTest.java index 8ef373c04..636fb2305 100644 --- a/backend/src/test/java/dev/tripdraw/domain/draw/RouteImageDrawerTest.java +++ b/backend/src/test/java/dev/tripdraw/draw/domain/RouteImageDrawerTest.java @@ -1,9 +1,12 @@ -package dev.tripdraw.domain.draw; +package dev.tripdraw.draw.domain; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; +import dev.tripdraw.draw.domain.Position; +import dev.tripdraw.draw.domain.Positions; +import dev.tripdraw.draw.domain.RouteImageDrawer; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; diff --git a/backend/src/test/java/dev/tripdraw/application/file/FileUploaderTest.java b/backend/src/test/java/dev/tripdraw/file/application/FileUploaderTest.java similarity index 80% rename from backend/src/test/java/dev/tripdraw/application/file/FileUploaderTest.java rename to backend/src/test/java/dev/tripdraw/file/application/FileUploaderTest.java index 454cde4f4..9b64bd814 100644 --- a/backend/src/test/java/dev/tripdraw/application/file/FileUploaderTest.java +++ b/backend/src/test/java/dev/tripdraw/file/application/FileUploaderTest.java @@ -1,5 +1,6 @@ -package dev.tripdraw.application.file; +package dev.tripdraw.file.application; +import static dev.tripdraw.file.domain.FileType.IMAGE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -8,8 +9,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import dev.tripdraw.domain.file.FileType; -import dev.tripdraw.exception.file.FileIOException; +import dev.tripdraw.file.exception.FileIOException; import java.io.File; import java.io.IOException; import java.util.UUID; @@ -44,10 +44,11 @@ class FileUploaderTest { String baseUrl = "https://example.com/files/"; String expectedFileUrl = baseUrl + randomUUID + ".jpg"; MultipartFile multipartFile = Mockito.mock(MultipartFile.class); + when(multipartFile.getContentType()).thenReturn(IMAGE.contentType()); when(fileUrlMaker.make(any())).thenReturn(expectedFileUrl); // when - String url = fileUploader.upload(multipartFile, FileType.POST_IMAGE); + String url = fileUploader.upload(multipartFile); // then assertThat(url).isEqualTo(expectedFileUrl); @@ -57,22 +58,24 @@ class FileUploaderTest { void 파일을_업로드_한다() throws IOException { // given MultipartFile multipartFile = Mockito.mock(MultipartFile.class); + when(multipartFile.getContentType()).thenReturn(IMAGE.contentType()); // when - fileUploader.upload(multipartFile, FileType.POST_IMAGE); + fileUploader.upload(multipartFile); // then verify(multipartFile, times(1)).transferTo(any(File.class)); } @Test - void 파일_저장에_실패할시_예외륿_발생시킨다() throws IOException { + void 파일_저장에_실패할시_예외를_발생시킨다() throws IOException { // given MultipartFile multipartFile = Mockito.mock(MultipartFile.class); + when(multipartFile.getContentType()).thenReturn(IMAGE.contentType()); doThrow(new IOException()).when(multipartFile).transferTo(any(File.class)); // expect - assertThatThrownBy(() -> fileUploader.upload(multipartFile, FileType.POST_IMAGE)) + assertThatThrownBy(() -> fileUploader.upload(multipartFile)) .isInstanceOf(FileIOException.class); } } diff --git a/backend/src/test/java/dev/tripdraw/application/file/FileUrlMakerTest.java b/backend/src/test/java/dev/tripdraw/file/application/FileUrlMakerTest.java similarity index 94% rename from backend/src/test/java/dev/tripdraw/application/file/FileUrlMakerTest.java rename to backend/src/test/java/dev/tripdraw/file/application/FileUrlMakerTest.java index 92e9e7fc4..8a45d5265 100644 --- a/backend/src/test/java/dev/tripdraw/application/file/FileUrlMakerTest.java +++ b/backend/src/test/java/dev/tripdraw/file/application/FileUrlMakerTest.java @@ -1,4 +1,4 @@ -package dev.tripdraw.application.file; +package dev.tripdraw.file.application; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/src/test/java/dev/tripdraw/member/application/MemberServiceTest.java b/backend/src/test/java/dev/tripdraw/member/application/MemberServiceTest.java new file mode 100644 index 000000000..d2a168944 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/member/application/MemberServiceTest.java @@ -0,0 +1,96 @@ +package dev.tripdraw.member.application; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberDeleteEvent; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.dto.MemberSearchResponse; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.test.ServiceTest; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripName; +import dev.tripdraw.trip.domain.TripRepository; +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.event.ApplicationEvents; +import org.springframework.test.context.event.RecordApplicationEvents; + +@RecordApplicationEvents +@ServiceTest +class MemberServiceTest { + + @Autowired + private MemberService memberService; + + @Autowired + private ApplicationEvents applicationEvents; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private TripRepository tripRepository; + + @Autowired + private PostRepository postRepository; + + private Member member; + private Trip trip; + + @BeforeEach + void setUp() { + member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + trip = tripRepository.save(new Trip(TripName.from("통후추의 여행"), member)); + Point point = new Point(3.14, 5.25, LocalDateTime.now()); + trip.add(point); + postRepository.save(new Post( + "제목", + point, + "위치", + "오늘은 날씨가 좋네요.", + member, + trip.id() + )); + } + + @Test + void 사용자를_조회한다() { + // given + LoginUser loginUser = new LoginUser(member.id()); + + // when + MemberSearchResponse response = memberService.find(loginUser); + + // expect + assertThat(response).usingRecursiveComparison().isEqualTo( + new MemberSearchResponse(member.id(), "통후추") + ); + } + + @Test + void 사용자를_삭제한다() { + // given + LoginUser loginUser = new LoginUser(member.id()); + + // when + memberService.delete(loginUser); + + // then + long publishedEvents = applicationEvents.stream(MemberDeleteEvent.class) + .filter(event -> event.memberId().equals(member.id())) + .count(); + + assertSoftly(softly -> { + softly.assertThat(memberRepository.findById(member.id())).isEmpty(); + softly.assertThat(publishedEvents).isOne(); + }); + } +} diff --git a/backend/src/test/java/dev/tripdraw/domain/member/MemberRepositoryTest.java b/backend/src/test/java/dev/tripdraw/member/domain/MemberRepositoryTest.java similarity index 54% rename from backend/src/test/java/dev/tripdraw/domain/member/MemberRepositoryTest.java rename to backend/src/test/java/dev/tripdraw/member/domain/MemberRepositoryTest.java index 1ec293db9..9ba306907 100644 --- a/backend/src/test/java/dev/tripdraw/domain/member/MemberRepositoryTest.java +++ b/backend/src/test/java/dev/tripdraw/member/domain/MemberRepositoryTest.java @@ -1,20 +1,25 @@ -package dev.tripdraw.domain.member; +package dev.tripdraw.member.domain; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import dev.tripdraw.common.config.JpaConfig; +import dev.tripdraw.common.config.QueryDslConfig; +import dev.tripdraw.member.exception.MemberException; import java.util.Optional; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @DataJpaTest +@Import({JpaConfig.class, QueryDslConfig.class}) class MemberRepositoryTest { @Autowired @@ -42,16 +47,26 @@ class MemberRepositoryTest { assertThat(foundMember).isEmpty(); } - @ParameterizedTest - @CsvSource({"통후추, true", "순후추, false"}) - void 닉네임이_존재하는지_확인한다(String nickname, boolean expected) { + @Test + void 회원_ID로_회원을_조회한다() { // given - memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); // when - boolean actual = memberRepository.existsByNickname(nickname); + Member foundMember = memberRepository.getById(member.id()); // then - assertThat(actual).isEqualTo(expected); + assertThat(foundMember).isEqualTo(member); + } + + @Test + void 회원_ID로_회원을_조회할_때_존재하지_않는_경우_예외를_발생시킨다() { + // given + Long wrongId = Long.MIN_VALUE; + + // expect + assertThatThrownBy(() -> memberRepository.getById(wrongId)) + .isInstanceOf(MemberException.class) + .hasMessage(MEMBER_NOT_FOUND.message()); } } diff --git a/backend/src/test/java/dev/tripdraw/domain/member/MemberTest.java b/backend/src/test/java/dev/tripdraw/member/domain/MemberTest.java similarity index 86% rename from backend/src/test/java/dev/tripdraw/domain/member/MemberTest.java rename to backend/src/test/java/dev/tripdraw/member/domain/MemberTest.java index f3b3d14ba..083c3f6c2 100644 --- a/backend/src/test/java/dev/tripdraw/domain/member/MemberTest.java +++ b/backend/src/test/java/dev/tripdraw/member/domain/MemberTest.java @@ -1,6 +1,6 @@ -package dev.tripdraw.domain.member; +package dev.tripdraw.member.domain; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.DisplayNameGeneration; diff --git a/backend/src/test/java/dev/tripdraw/presentation/controller/MemberControllerTest.java b/backend/src/test/java/dev/tripdraw/member/presentation/MemberControllerTest.java similarity index 56% rename from backend/src/test/java/dev/tripdraw/presentation/controller/MemberControllerTest.java rename to backend/src/test/java/dev/tripdraw/member/presentation/MemberControllerTest.java index 44a69bec6..03a5d11ee 100644 --- a/backend/src/test/java/dev/tripdraw/presentation/controller/MemberControllerTest.java +++ b/backend/src/test/java/dev/tripdraw/member/presentation/MemberControllerTest.java @@ -1,23 +1,22 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.member.presentation; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static java.lang.Long.MIN_VALUE; +import static dev.tripdraw.common.auth.OauthType.KAKAO; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.springframework.http.HttpStatus.FORBIDDEN; -import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.OK; -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.post.PostRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripName; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.member.MemberSearchResponse; +import dev.tripdraw.auth.application.JwtTokenProvider; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.dto.MemberSearchResponse; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.test.ControllerTest; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripName; +import dev.tripdraw.trip.domain.TripRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; @@ -46,23 +45,23 @@ class MemberControllerTest extends ControllerTest { PostRepository postRepository; @Autowired - AuthTokenManager authTokenManager; + JwtTokenProvider jwtTokenProvider; @BeforeEach - void setUp() { + public void setUp() { RestAssured.port = port; } @Test - void code를_입력_받아_사용자를_조회한다() { + void 사용자를_조회한다() { // given Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - String code = authTokenManager.generate(member.id()); + String huchuToken = jwtTokenProvider.generateAccessToken(member.id().toString()); // when ExtractableResponse response = RestAssured.given().log().all() - .param("code", code) - .when().get("/members") + .auth().preemptive().oauth2(huchuToken) + .when().get("/members/me") .then().log().all() .extract(); @@ -78,39 +77,10 @@ void setUp() { } @Test - void code를_입력_받아_사용자를_조회할_때_존재하지_않는_사용자라면_예외가_발생한다() { - // given - String code = authTokenManager.generate(MIN_VALUE); - - // expect - RestAssured.given().log().all() - .param("code", code) - .when().get("/members") - .then().log().all() - .statusCode(NOT_FOUND.value()); - } - - @Test - void code를_입력_받아_사용자를_조회할_때_이미_삭제된_사용자라면_예외가_발생한다() { - // given - Member member = memberRepository.save(new Member("순후추", "kakaoId", KAKAO)); - String code = authTokenManager.generate(member.id()); - - memberRepository.delete(member); - - // expect - RestAssured.given().log().all() - .param("code", code) - .when().get("/members") - .then().log().all() - .statusCode(NOT_FOUND.value()); - } - - @Test - void code를_입력_받아_사용자를_삭제한다() { + void 사용자를_삭제한다() { // given Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - String code = authTokenManager.generate(member.id()); + String huchuToken = jwtTokenProvider.generateAccessToken(member.id().toString()); Trip trip = new Trip(TripName.from("통후추의 여행"), member); Point point = new Point(3.14, 5.25, LocalDateTime.now()); @@ -127,19 +97,19 @@ void setUp() { // expect RestAssured.given().log().all() - .param("code", code) - .when().delete("/members") + .auth().preemptive().oauth2(huchuToken) + .when().delete("/members/me") .then().log().all() .statusCode(NO_CONTENT.value()); RestAssured.given().log().all() - .auth().preemptive().oauth2(code) + .auth().preemptive().oauth2(huchuToken) .when().get("/trips/{tripId}", trip.id()) .then().log().all() .statusCode(FORBIDDEN.value()); RestAssured.given().log().all() - .auth().preemptive().oauth2(code) + .auth().preemptive().oauth2(huchuToken) .when().get("/posts/{postId}", post.id()) .then().log().all() .statusCode(FORBIDDEN.value()); diff --git a/backend/src/test/java/dev/tripdraw/post/application/PostDeleteEventHandlerTest.java b/backend/src/test/java/dev/tripdraw/post/application/PostDeleteEventHandlerTest.java new file mode 100644 index 000000000..32f3f5e85 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/post/application/PostDeleteEventHandlerTest.java @@ -0,0 +1,40 @@ +package dev.tripdraw.post.application; + +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +import dev.tripdraw.member.domain.MemberDeleteEvent; +import dev.tripdraw.post.domain.PostRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@ExtendWith(MockitoExtension.class) +class PostDeleteEventHandlerTest { + + @Mock + private PostRepository postRepository; + + @InjectMocks + private PostDeleteEventHandler postDeleteEventHandler; + + @Test + void 회원_삭제_이벤트를_받아_회원의_감상을_삭제한다() { + // given + MemberDeleteEvent memberDeleteEvent = new MemberDeleteEvent(1L); + + // when + postDeleteEventHandler.deletePostByMemberId(memberDeleteEvent); + + // then + then(postRepository) + .should(times(1)) + .deleteByMemberId(1L); + } +} diff --git a/backend/src/test/java/dev/tripdraw/application/PostServiceTest.java b/backend/src/test/java/dev/tripdraw/post/application/PostServiceTest.java similarity index 62% rename from backend/src/test/java/dev/tripdraw/application/PostServiceTest.java rename to backend/src/test/java/dev/tripdraw/post/application/PostServiceTest.java index ffacfd083..99064cd4e 100644 --- a/backend/src/test/java/dev/tripdraw/application/PostServiceTest.java +++ b/backend/src/test/java/dev/tripdraw/post/application/PostServiceTest.java @@ -1,43 +1,55 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; -import static dev.tripdraw.exception.post.PostExceptionType.NOT_AUTHORIZED_TO_POST; -import static dev.tripdraw.exception.post.PostExceptionType.POST_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.NOT_AUTHORIZED_TO_TRIP; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_NOT_FOUND; -import static java.lang.Long.MIN_VALUE; +package dev.tripdraw.post.application; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; +import static dev.tripdraw.post.exception.PostExceptionType.NOT_AUTHORIZED_TO_POST; +import static dev.tripdraw.post.exception.PostExceptionType.POST_NOT_FOUND; +import static dev.tripdraw.trip.exception.TripExceptionType.POINT_NOT_FOUND; +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; - -import dev.tripdraw.application.draw.RouteImageGenerator; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.dto.post.PostAndPointCreateRequest; -import dev.tripdraw.dto.post.PostCreateResponse; -import dev.tripdraw.dto.post.PostRequest; -import dev.tripdraw.dto.post.PostResponse; -import dev.tripdraw.dto.post.PostUpdateRequest; -import dev.tripdraw.dto.post.PostsResponse; -import dev.tripdraw.exception.member.MemberException; -import dev.tripdraw.exception.post.PostException; -import dev.tripdraw.exception.trip.TripException; -import java.time.LocalDateTime; -import java.util.List; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.draw.application.RouteImageGenerator; +import dev.tripdraw.file.application.FileUploader; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.exception.MemberException; +import dev.tripdraw.post.dto.PostAndPointCreateRequest; +import dev.tripdraw.post.dto.PostCreateResponse; +import dev.tripdraw.post.dto.PostRequest; +import dev.tripdraw.post.dto.PostResponse; +import dev.tripdraw.post.dto.PostSearchRequest; +import dev.tripdraw.post.dto.PostSearchResponse; +import dev.tripdraw.post.dto.PostUpdateRequest; +import dev.tripdraw.post.dto.PostsResponse; +import dev.tripdraw.post.dto.PostsSearchResponse; +import dev.tripdraw.post.exception.PostException; +import dev.tripdraw.test.ServiceTest; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.PointRepository; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.exception.TripException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; -import org.mockito.BDDMockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @ServiceTest class PostServiceTest { @@ -47,12 +59,18 @@ class PostServiceTest { @Autowired private TripRepository tripRepository; + @Autowired + private PointRepository pointRepository; + @Autowired private MemberRepository memberRepository; @MockBean private RouteImageGenerator routeImageGenerator; + @MockBean + private FileUploader fileUploader; + private Trip trip; private LoginUser loginUser; private LoginUser otherUser; @@ -64,8 +82,8 @@ void setUp() { Member otherMember = memberRepository.save(new Member("순후추", "kakaoId", KAKAO)); trip = tripRepository.save(Trip.from(member)); point = new Point(1.1, 2.1, LocalDateTime.now()); - trip.add(point); - tripRepository.flush(); + point.setTrip(trip); + pointRepository.save(point); loginUser = new LoginUser(member.id()); otherUser = new LoginUser(otherMember.id()); } @@ -73,10 +91,10 @@ void setUp() { @Test void 현재_위치에_대한_감상을_생성한다() { // given - PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( + PostAndPointCreateRequest request = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -84,8 +102,7 @@ void setUp() { ); // when - PostCreateResponse postCreateResponse = postService.addAtCurrentPoint(loginUser, postAndPointCreateRequest, - null); + PostCreateResponse postCreateResponse = postService.addAtCurrentPoint(loginUser, request, null); // then assertThat(postCreateResponse.postId()).isNotNull(); @@ -94,11 +111,11 @@ void setUp() { @Test void 현재_위치에_대한_감상을_생성할_때_존재하지_않는_사용자_닉네임이면_예외를_발생시킨다() { // given - LoginUser wrongUser = new LoginUser(MIN_VALUE); + LoginUser wrongUser = new LoginUser(Long.MIN_VALUE); PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -115,9 +132,9 @@ void setUp() { void 현재_위치에_대한_감상을_생성할_때_존재하지_않는_여행의_ID이면_예외를_발생시킨다() { // given PostAndPointCreateRequest requestOfNotExistedTripId = new PostAndPointCreateRequest( - MIN_VALUE, + Long.MIN_VALUE, "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -137,10 +154,10 @@ void setUp() { trip.id(), point.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); - BDDMockito.given(routeImageGenerator.generate(any(), any(), any(), any())).willReturn("hello.png"); + given(routeImageGenerator.generate(any(), any(), any(), any())).willReturn("hello.png"); // when PostCreateResponse postCreateResponse = postService.addAtExistingLocation(loginUser, postRequest, null); @@ -152,12 +169,12 @@ void setUp() { @Test void 사용자가_선택한_위치에_대한_감상을_생성할_때_존재하지_않는_사용자_닉네임이면_예외를_발생시킨다() { // given - LoginUser wrongUser = new LoginUser(MIN_VALUE); + LoginUser wrongUser = new LoginUser(Long.MIN_VALUE); PostRequest postRequest = new PostRequest( trip.id(), point.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -171,10 +188,10 @@ void setUp() { void 사용자가_선택한_위치에_대한_감상을_생성할_때_존재하지_않는_여행의_ID이면_예외를_발생시킨다() { // given PostRequest requestOfNotExistedTripId = new PostRequest( - MIN_VALUE, + Long.MIN_VALUE, point.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -189,9 +206,9 @@ void setUp() { // given PostRequest requestOfNotExistedPointId = new PostRequest( trip.id(), - MIN_VALUE, + Long.MIN_VALUE, "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -204,10 +221,10 @@ void setUp() { @Test void 특정_감상을_조회한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); // when - PostResponse postResponse = postService.read(loginUser, postCreateResponse.postId()); + PostResponse postResponse = postService.read(postCreateResponse.postId()); // then assertSoftly(softly -> { @@ -219,87 +236,72 @@ void setUp() { @Test void 특정_감상을_조회할_때_존재하지_않는_감상_ID이면_예외를_발생시킨다() { - // given & expect - assertThatThrownBy(() -> postService.read(loginUser, MIN_VALUE)) - .isInstanceOf(PostException.class) - .hasMessage(POST_NOT_FOUND.message()); - } - - @Test - void 특정_감상을_조회할_때_존재하지_않는_사용자_닉네임이면_예외를_발생시킨다() { - // given - PostCreateResponse postCreateResponse = createPost(); - LoginUser wrongUser = new LoginUser(MIN_VALUE); - // expect - assertThatThrownBy(() -> postService.read(wrongUser, postCreateResponse.postId())) - .isInstanceOf(MemberException.class) - .hasMessage(MEMBER_NOT_FOUND.message()); - } - - @Test - void 특정_감상을_조회할_때_로그인_한_사용자가_감상의_작성자가_아니면_예외가_발생한다() { - // given - PostCreateResponse postCreateResponse = createPost(); - - // expect - assertThatThrownBy(() -> postService.read(otherUser, postCreateResponse.postId())) + assertThatThrownBy(() -> postService.read(Long.MIN_VALUE)) .isInstanceOf(PostException.class) - .hasMessage(NOT_AUTHORIZED_TO_POST.message()); + .hasMessage(POST_NOT_FOUND.message()); } @Test void 특정_여행의_모든_감상을_조회한다() { // given - createPost(); - createPost2(); + createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); + createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); // when - PostsResponse postsResponse = postService.readAllByTripId(loginUser, trip.id()); + PostsResponse postsResponse = postService.readAllByTripId(trip.id()); // then List posts = postsResponse.posts(); assertSoftly(softly -> { softly.assertThat(posts.get(0).postId()).isNotNull(); - softly.assertThat(posts.get(0).title()).isEqualTo("우도의 땅콩 아이스크림"); softly.assertThat(posts.get(0).pointResponse().pointId()).isNotNull(); softly.assertThat(posts.get(1).postId()).isNotNull(); - softly.assertThat(posts.get(1).title()).isEqualTo("우도의 바닷가"); softly.assertThat(posts.get(1).pointResponse().pointId()).isNotNull(); }); } - @Test - void 특정_여행의_모든_감상을_조회할_때_존재하지_않는_사용자_닉네임이면_예외를_발생시킨다() { - // given - LoginUser wrongUser = new LoginUser(MIN_VALUE); - - // expect - assertThatThrownBy(() -> postService.readAllByTripId(wrongUser, trip.id())) - .isInstanceOf(MemberException.class) - .hasMessage(MEMBER_NOT_FOUND.message()); - } - @Test void 특정_여행의_모든_감상을_조회할_때_존재하지_않는_여행_ID이면_예외가_발생한다() { - // given & expect - assertThatThrownBy(() -> postService.readAllByTripId(loginUser, MIN_VALUE)) + // expect + assertThatThrownBy(() -> postService.readAllByTripId(Long.MIN_VALUE)) .isInstanceOf(TripException.class) .hasMessage(TRIP_NOT_FOUND.message()); } @Test - void 특정_여행의_모든_감상을_조회할_때_로그인_한_사용자가_여행의_주인이_아니면_예외가_발생한다() { - // given & expect - assertThatThrownBy(() -> postService.readAllByTripId(otherUser, trip.id())) - .isInstanceOf(TripException.class) - .hasMessage(NOT_AUTHORIZED_TO_TRIP.message()); + void 조건에_해당하는_모든_여행을_조회한다() { + // given + PostCreateResponse jejuMay = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 5, 12, 15, 30)); + PostCreateResponse jejuJuly = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 12, 15, 30)); + PostCreateResponse seoulJuly = createPost("서울특별시 송파구 문정동", LocalDateTime.of(2023, 7, 12, 15, 30)); + + PostSearchRequest postSearchRequestJeju = PostSearchRequest.builder() + .address("제주특별자치도 제주시 애월읍") + .limit(10) + .build(); + + PostSearchRequest postSearchRequestJuly = PostSearchRequest.builder() + .months(Set.of(7)) + .limit(10) + .build(); + + // when + PostsSearchResponse postsSearchJejuResponse = postService.readAll(postSearchRequestJeju); + PostsSearchResponse postsSearchJulyResponse = postService.readAll(postSearchRequestJuly); + + // then + assertThat(postsSearchJejuResponse.posts().stream().map(PostSearchResponse::postId).toList()).containsExactly( + jejuJuly.postId(), jejuMay.postId()); + assertThat(postsSearchJulyResponse.posts().stream().map(PostSearchResponse::postId).toList()).containsExactly( + seoulJuly.postId(), jejuJuly.postId()); + } @Test void 감상을_수정한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); PostUpdateRequest postUpdateRequest = new PostUpdateRequest( "우도의 땅콩 아이스크림", "수정한 내용입니다." @@ -309,7 +311,7 @@ void setUp() { postService.update(loginUser, postCreateResponse.postId(), postUpdateRequest, null); // then - PostResponse postResponseBeforeUpdate = postService.read(loginUser, postCreateResponse.postId()); + PostResponse postResponseBeforeUpdate = postService.read(postCreateResponse.postId()); assertSoftly(softly -> { softly.assertThat(postResponseBeforeUpdate.postId()).isEqualTo(postCreateResponse.postId()); @@ -321,14 +323,13 @@ void setUp() { @Test void 감상을_수정할_때_존재하지_않는_감상_ID이면_예외를_발생시킨다() { // given - PostCreateResponse postCreateResponse = createPost(); PostUpdateRequest postUpdateRequest = new PostUpdateRequest( "우도의 땅콩 아이스크림", "수정한 내용입니다." ); // expect - assertThatThrownBy(() -> postService.update(loginUser, MIN_VALUE, postUpdateRequest, null)) + assertThatThrownBy(() -> postService.update(loginUser, Long.MIN_VALUE, postUpdateRequest, null)) .isInstanceOf(PostException.class) .hasMessage(POST_NOT_FOUND.message()); } @@ -336,15 +337,16 @@ void setUp() { @Test void 감상을_수정할_때_존재하지_않는_사용자_닉네임이면_예외를_발생시킨다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); PostUpdateRequest postUpdateRequest = new PostUpdateRequest( "우도의 땅콩 아이스크림", "수정한 내용입니다." ); - LoginUser wrongUser = new LoginUser(MIN_VALUE); + LoginUser wrongUser = new LoginUser(Long.MIN_VALUE); // expect - assertThatThrownBy(() -> postService.update(wrongUser, postCreateResponse.postId(), postUpdateRequest, null)) + Long postId = postCreateResponse.postId(); + assertThatThrownBy(() -> postService.update(wrongUser, postId, postUpdateRequest, null)) .isInstanceOf(MemberException.class) .hasMessage(MEMBER_NOT_FOUND.message()); } @@ -352,14 +354,15 @@ void setUp() { @Test void 감상을_수정할_때_로그인_한_사용자가_감상의_작성자가_아니면_예외가_발생한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); PostUpdateRequest postUpdateRequest = new PostUpdateRequest( "우도의 땅콩 아이스크림", "수정한 내용입니다." ); // expect - assertThatThrownBy(() -> postService.update(otherUser, postCreateResponse.postId(), postUpdateRequest, null)) + Long postId = postCreateResponse.postId(); + assertThatThrownBy(() -> postService.update(otherUser, postId, postUpdateRequest, null)) .isInstanceOf(PostException.class) .hasMessage(NOT_AUTHORIZED_TO_POST.message()); } @@ -367,20 +370,21 @@ void setUp() { @Test void 감상을_삭제한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); // expect - assertDoesNotThrow(() -> postService.delete(loginUser, postCreateResponse.postId())); + Long postId = postCreateResponse.postId(); + assertDoesNotThrow(() -> postService.delete(loginUser, postId)); - assertThatThrownBy(() -> postService.read(loginUser, postCreateResponse.postId())) + assertThatThrownBy(() -> postService.read(postId)) .isInstanceOf(PostException.class) .hasMessage(POST_NOT_FOUND.message()); } @Test void 감상을_삭제할_때_존재하지_않는_감상_ID이면_예외를_발생시킨다() { - // given & expect - assertThatThrownBy(() -> postService.delete(loginUser, MIN_VALUE)) + // expect + assertThatThrownBy(() -> postService.delete(loginUser, Long.MIN_VALUE)) .isInstanceOf(PostException.class) .hasMessage(POST_NOT_FOUND.message()); } @@ -388,11 +392,12 @@ void setUp() { @Test void 감상을_삭제할_때_존재하지_않는_사용자_닉네임이면_예외를_발생시킨다() { // given - PostCreateResponse postCreateResponse = createPost(); - LoginUser wrongUser = new LoginUser(MIN_VALUE); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); + LoginUser wrongUser = new LoginUser(Long.MIN_VALUE); // expect - assertThatThrownBy(() -> postService.delete(wrongUser, postCreateResponse.postId())) + Long postId = postCreateResponse.postId(); + assertThatThrownBy(() -> postService.delete(wrongUser, postId)) .isInstanceOf(MemberException.class) .hasMessage(MEMBER_NOT_FOUND.message()); } @@ -400,39 +405,46 @@ void setUp() { @Test void 감상을_삭제할_때_로그인_한_사용자가_감상의_작성자가_아니면_예외가_발생한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); // expect - assertThatThrownBy(() -> postService.delete(otherUser, postCreateResponse.postId())) + Long postId = postCreateResponse.postId(); + assertThatThrownBy(() -> postService.delete(otherUser, postId)) .isInstanceOf(PostException.class) .hasMessage(NOT_AUTHORIZED_TO_POST.message()); } - private PostCreateResponse createPost() { - PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( + @Test + void 이미지를_저장하는_경우_여행의_대표이미지도_변경한다() { + // given + PostAndPointCreateRequest request = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, LocalDateTime.of(2023, 7, 18, 20, 24) ); + MultipartFile multipartFile = mock(MultipartFile.class); + given(fileUploader.upload(any())).willReturn("hello.png"); - return postService.addAtCurrentPoint(loginUser, postAndPointCreateRequest, null); + // when + postService.addAtCurrentPoint(loginUser, request, multipartFile); + + // then + assertThat(trip.imageUrl()).isEqualTo("hello.png"); } - private PostCreateResponse createPost2() { + private PostCreateResponse createPost(String address, LocalDateTime localDateTime) { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), - "우도의 땅콩 아이스크림", - "제주특별자치도 제주시 애월읍 소길리", + "우도의 바닷가", + address, "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", - 1.2, + 1.1, 2.2, - LocalDateTime.of(2023, 7, 20, 12, 13) - ); - + localDateTime); return postService.addAtCurrentPoint(loginUser, postAndPointCreateRequest, null); } } diff --git a/backend/src/test/java/dev/tripdraw/post/domain/PostCustomRepositoryImplTest.java b/backend/src/test/java/dev/tripdraw/post/domain/PostCustomRepositoryImplTest.java new file mode 100644 index 000000000..2e7533668 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/post/domain/PostCustomRepositoryImplTest.java @@ -0,0 +1,90 @@ +package dev.tripdraw.post.domain; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static org.assertj.core.api.Assertions.assertThat; + +import dev.tripdraw.common.config.JpaConfig; +import dev.tripdraw.common.config.QueryDslConfig; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.post.dto.PostSearchConditions; +import dev.tripdraw.post.dto.PostSearchPaging; +import dev.tripdraw.post.query.PostCustomRepository; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@Transactional +@SpringBootTest +@Import({JpaConfig.class, QueryDslConfig.class}) +class PostCustomRepositoryImplTest { + + @Autowired + private PostCustomRepository postCustomRepository; + + @Autowired + private TripRepository tripRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private PostRepository postRepository; + + @Test + void 조건에_해당하는_감상을_조회한다() { + // given + Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + Trip trip = Trip.from(member); + + Point firstPoint = new Point(3.14, 5.25, LocalDateTime.of(2023, 5, 1, 17, 30)); + Point secondPoint = new Point(3.14, 5.25, LocalDateTime.of(2023, 5, 3, 18, 30)); + Point thirdPoint = new Point(3.14, 5.25, LocalDateTime.of(2023, 7, 1, 18, 30)); + + trip.add(firstPoint); + trip.add(secondPoint); + trip.add(thirdPoint); + + tripRepository.save(trip); + + Post firstPost = new Post("제목", firstPoint, "위치", "오늘은 날씨가 좋네요.", member, trip.id()); + Post secondPost = new Post("제목", secondPoint, "위치", "오늘은 날씨가 좋네요.", member, trip.id()); + Post thirdPost = new Post("제목", thirdPoint, "위치", "오늘은 날씨가 좋네요.", member, trip.id()); + + postRepository.save(firstPost); + postRepository.save(secondPost); + postRepository.save(thirdPost); + + PostSearchConditions firstConditions = PostSearchConditions.builder() + .months(Set.of(5)) + .build(); + + PostSearchConditions secondConditions = PostSearchConditions.builder() + .hours(Set.of(18)) + .build(); + + PostSearchPaging paging = new PostSearchPaging(null, 10); + + // when + List firstPosts = postCustomRepository.findAllByConditions(firstConditions, paging); + List secondPosts = postCustomRepository.findAllByConditions(secondConditions, paging); + + // then + assertThat(firstPosts.stream().map(Post::id).toList()).containsExactly(secondPost.id(), firstPost.id()); + assertThat(secondPosts.stream().map(Post::id).toList()).containsExactly(thirdPost.id(), secondPost.id()); + } +} + diff --git a/backend/src/test/java/dev/tripdraw/domain/post/PostRepositoryTest.java b/backend/src/test/java/dev/tripdraw/post/domain/PostRepositoryTest.java similarity index 63% rename from backend/src/test/java/dev/tripdraw/domain/post/PostRepositoryTest.java rename to backend/src/test/java/dev/tripdraw/post/domain/PostRepositoryTest.java index fb251a4de..f795e8f36 100644 --- a/backend/src/test/java/dev/tripdraw/domain/post/PostRepositoryTest.java +++ b/backend/src/test/java/dev/tripdraw/post/domain/PostRepositoryTest.java @@ -1,30 +1,33 @@ -package dev.tripdraw.domain.post; +package dev.tripdraw.post.domain; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.post.exception.PostExceptionType.POST_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripName; -import dev.tripdraw.domain.trip.TripRepository; +import dev.tripdraw.common.config.JpaConfig; +import dev.tripdraw.common.config.QueryDslConfig; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.post.exception.PostException; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripName; +import dev.tripdraw.trip.domain.TripRepository; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.BeforeEach; -import static dev.tripdraw.exception.post.PostExceptionType.POST_NOT_FOUND; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import dev.tripdraw.exception.post.PostException; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @DataJpaTest +@Import({JpaConfig.class, QueryDslConfig.class}) class PostRepositoryTest { @Autowired @@ -75,9 +78,24 @@ void setUp() { } @Test - void 감상_ID에_대한_감상이_존재하지_않는다면_예외를_던진다() { + void 감상_ID로_감상을_조회한다() { + // given + Post post = postRepository.save(new Post("제목", point, "위치", "오늘은 날씨가 좋네요.", member, trip.id())); + + // when + Post foundPost = postRepository.getByPostId(post.id()); + + // then + assertThat(foundPost).isEqualTo(post); + } + + @Test + void 감상_ID로_감상을_조회할_때_존재하지_않는_경우_예외를_발생시킨다() { + // given + Long wrongId = Long.MIN_VALUE; + // expect - assertThatThrownBy(() -> postRepository.getById(1L)) + assertThatThrownBy(() -> postRepository.getByPostId(wrongId)) .isInstanceOf(PostException.class) .hasMessage(POST_NOT_FOUND.message()); } diff --git a/backend/src/test/java/dev/tripdraw/domain/post/PostTest.java b/backend/src/test/java/dev/tripdraw/post/domain/PostTest.java similarity index 93% rename from backend/src/test/java/dev/tripdraw/domain/post/PostTest.java rename to backend/src/test/java/dev/tripdraw/post/domain/PostTest.java index fc478c1c3..5e8f673a0 100644 --- a/backend/src/test/java/dev/tripdraw/domain/post/PostTest.java +++ b/backend/src/test/java/dev/tripdraw/post/domain/PostTest.java @@ -1,16 +1,16 @@ -package dev.tripdraw.domain.post; +package dev.tripdraw.post.domain; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.exception.post.PostExceptionType.NOT_AUTHORIZED_TO_POST; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_HAS_POST; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.post.exception.PostExceptionType.NOT_AUTHORIZED_TO_POST; +import static dev.tripdraw.trip.exception.TripExceptionType.POINT_ALREADY_HAS_POST; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.exception.post.PostException; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.post.exception.PostException; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.exception.TripException; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; diff --git a/backend/src/test/java/dev/tripdraw/dto/post/PostResponseAndPointCreateRequestTest.java b/backend/src/test/java/dev/tripdraw/post/dto/PostResponseAndPointCreateRequestTest.java similarity index 85% rename from backend/src/test/java/dev/tripdraw/dto/post/PostResponseAndPointCreateRequestTest.java rename to backend/src/test/java/dev/tripdraw/post/dto/PostResponseAndPointCreateRequestTest.java index 5273a6d79..4c9f21c90 100644 --- a/backend/src/test/java/dev/tripdraw/dto/post/PostResponseAndPointCreateRequestTest.java +++ b/backend/src/test/java/dev/tripdraw/post/dto/PostResponseAndPointCreateRequestTest.java @@ -1,13 +1,15 @@ -package dev.tripdraw.dto.post; +package dev.tripdraw.post.dto; import static org.assertj.core.api.Assertions.assertThat; -import dev.tripdraw.domain.trip.Point; -import java.time.LocalDateTime; +import dev.tripdraw.trip.domain.Point; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; +import java.time.LocalDateTime; + +@SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class PostResponseAndPointCreateRequestTest { @@ -17,7 +19,7 @@ class PostResponseAndPointCreateRequestTest { PostAndPointCreateRequest request = new PostAndPointCreateRequest( 1L, "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, diff --git a/backend/src/test/java/dev/tripdraw/presentation/controller/PostControllerTest.java b/backend/src/test/java/dev/tripdraw/post/presentation/PostControllerTest.java similarity index 77% rename from backend/src/test/java/dev/tripdraw/presentation/controller/PostControllerTest.java rename to backend/src/test/java/dev/tripdraw/post/presentation/PostControllerTest.java index 6e490e2a3..f28b976d3 100644 --- a/backend/src/test/java/dev/tripdraw/presentation/controller/PostControllerTest.java +++ b/backend/src/test/java/dev/tripdraw/post/presentation/PostControllerTest.java @@ -1,6 +1,7 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.post.presentation; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CREATED; @@ -11,26 +12,30 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; -import dev.tripdraw.application.draw.RouteImageGenerator; -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.post.PostAndPointCreateRequest; -import dev.tripdraw.dto.post.PostCreateResponse; -import dev.tripdraw.dto.post.PostRequest; -import dev.tripdraw.dto.post.PostResponse; -import dev.tripdraw.dto.post.PostUpdateRequest; -import dev.tripdraw.dto.post.PostsResponse; -import dev.tripdraw.dto.trip.PointCreateRequest; -import dev.tripdraw.dto.trip.PointResponse; +import dev.tripdraw.auth.application.JwtTokenProvider; +import dev.tripdraw.draw.application.RouteImageGenerator; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.post.dto.PostAndPointCreateRequest; +import dev.tripdraw.post.dto.PostCreateResponse; +import dev.tripdraw.post.dto.PostRequest; +import dev.tripdraw.post.dto.PostResponse; +import dev.tripdraw.post.dto.PostUpdateRequest; +import dev.tripdraw.post.dto.PostsResponse; +import dev.tripdraw.post.dto.PostsSearchResponse; +import dev.tripdraw.test.ControllerTest; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.PointResponse; import io.restassured.RestAssured; import io.restassured.builder.MultiPartSpecBuilder; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import io.restassured.specification.MultiPartSpecification; import java.time.LocalDateTime; +import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -38,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +@SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class PostControllerTest extends ControllerTest { @@ -50,8 +56,9 @@ class PostControllerTest extends ControllerTest { private MemberRepository memberRepository; @Autowired - private AuthTokenManager authTokenManager; + private JwtTokenProvider jwtTokenProvider; + // 이벤트를 통해 비동기로 경로 이미지를 생성하기 때문에, MockBean으로 둡니다. @MockBean private RouteImageGenerator routeImageGenerator; @@ -59,12 +66,12 @@ class PostControllerTest extends ControllerTest { private String huchuToken; @BeforeEach - void setUp() { + public void setUp() { super.setUp(); Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); trip = tripRepository.save(Trip.from(member)); - huchuToken = authTokenManager.generate(member.id()); + huchuToken = jwtTokenProvider.generateAccessToken(member.id().toString()); } @Test @@ -73,7 +80,7 @@ void setUp() { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -104,7 +111,7 @@ void setUp() { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -127,7 +134,7 @@ void setUp() { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( Long.MIN_VALUE, "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -150,7 +157,7 @@ void setUp() { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -173,7 +180,7 @@ void setUp() { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "a".repeat(101), - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, @@ -196,7 +203,7 @@ void setUp() { PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", null, 2.2, @@ -222,7 +229,7 @@ void setUp() { trip.id(), pointResponse.pointId(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -253,7 +260,7 @@ void setUp() { trip.id(), pointResponse.pointId(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -276,7 +283,7 @@ void setUp() { Long.MIN_VALUE, pointResponse.pointId(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -297,7 +304,7 @@ void setUp() { trip.id(), Long.MIN_VALUE, "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -343,7 +350,7 @@ void setUp() { trip.id(), pointResponse.pointId(), "a".repeat(101), - "제주특별자치도 제주시 애월읍 소길리", + "제주특별자치도 제주시 애월읍", "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다." ); @@ -360,7 +367,7 @@ void setUp() { @Test void 특정_감상을_조회한다() { // given - PostCreateResponse postResponse = createPost(); + PostCreateResponse postResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 20, 24)); // when ExtractableResponse findResponse = RestAssured.given().log().all() @@ -379,14 +386,15 @@ void setUp() { softly.assertThat(getResponse.title()).isEqualTo("우도의 바닷가"); softly.assertThat(getResponse.pointResponse().pointId()).isNotNull(); softly.assertThat(getResponse.pointResponse().latitude()).isEqualTo(1.1); - softly.assertThat(getResponse.postImageUrl()).isNull(); + softly.assertThat(getResponse.postImageUrl()).isEmpty(); + softly.assertThat(getResponse.routeImageUrl()).isEmpty(); }); } @Test void 특정_감상을_조회할_때_인증에_실패하면_예외가_발생한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 20, 24)); // expect RestAssured.given().log().all() @@ -411,8 +419,8 @@ void setUp() { @Test void 특정_여행에_대한_모든_감상을_조회한다() { // given - createPost(); - createPost(); + createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 20, 24)); + createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 21, 24)); // when ExtractableResponse findResponse = RestAssured.given().log().all() @@ -463,7 +471,7 @@ void setUp() { @Test void 감상을_수정한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.now()); PostUpdateRequest postUpdateRequest = new PostUpdateRequest( "우도의 땅콩 아이스크림", @@ -494,7 +502,7 @@ void setUp() { @Test void 감상을_수정할_때_인증에_실패하면_예외가_발생한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 20, 24)); PostUpdateRequest postUpdateRequest = new PostUpdateRequest( "우도의 땅콩 아이스크림", @@ -536,7 +544,7 @@ void setUp() { @Test void 감상을_삭제한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 20, 24)); // expect1 : 삭제하면 204 NO_CONTENT 응답 RestAssured.given().log().all() @@ -558,7 +566,7 @@ void setUp() { @Test void 감상을_삭제할_때_인증에_실패하면_예외가_발생한다() { // given - PostCreateResponse postCreateResponse = createPost(); + PostCreateResponse postCreateResponse = createPost("제주특별자치도 제주시 애월읍", LocalDateTime.of(2023, 7, 18, 20, 24)); // expect RestAssured.given().log().all() @@ -580,6 +588,80 @@ void setUp() { .statusCode(NOT_FOUND.value()); } + @Test + void 다른_사용자들의_감상을_조회한다() { + // given + PostCreateResponse jejuJuly20hourPostResponse = createPost("제주특별자치도 제주시 애월읍", + LocalDateTime.of(2023, 7, 18, 20, 24)); + PostCreateResponse jejuAugust17hourPostResponse = createPost("제주특별자치도 제주시 애월읍", + LocalDateTime.of(2023, 8, 18, 17, 24)); + PostCreateResponse jejuSeptember17hourPostResponse = createPost("제주특별자치도 제주시 애월읍", + LocalDateTime.of(2023, 9, 18, 17, 24)); + PostCreateResponse seoulSeptember17hourPostResponse = createPost("서울특별시 송파구 잠실동", + LocalDateTime.of(2023, 9, 18, 17, 24)); + + Map jejuParams = Map.of( + "address", "제주특별자치도 제주시 애월읍", + "limit", 10 + ); + + Map jejuHour17Params = Map.of( + "hours", Set.of(17), + "address", "제주특별자치도 제주시 애월읍", + "limit", 10 + ); + + Map hour17Params = Map.of( + "hours", Set.of(17), + "limit", 10 + ); + + // when + ExtractableResponse jejuResponse = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(jejuParams) + .when().get("/posts") + .then().log().all() + .statusCode(OK.value()) + .extract(); + + ExtractableResponse jejuhour17Response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(jejuHour17Params) + .when().get("/posts") + .then().log().all() + .statusCode(OK.value()) + .extract(); + + ExtractableResponse hour17Response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(hour17Params) + .when().get("/posts") + .then().log().all() + .statusCode(OK.value()) + .extract(); + + // then + PostsSearchResponse jejuPostsSearchResponse = jejuResponse.as(PostsSearchResponse.class); + PostsSearchResponse jeju17hourPostsSearchResponse = jejuhour17Response.as(PostsSearchResponse.class); + PostsSearchResponse hour17PostsSearchResponse = hour17Response.as(PostsSearchResponse.class); + + assertThat(jejuPostsSearchResponse.posts().get(0).postId()).isEqualTo(jejuSeptember17hourPostResponse.postId()); + assertThat(jejuPostsSearchResponse.posts().get(1).postId()).isEqualTo(jejuAugust17hourPostResponse.postId()); + assertThat(jejuPostsSearchResponse.posts().get(2).postId()).isEqualTo(jejuJuly20hourPostResponse.postId()); + + assertThat(jeju17hourPostsSearchResponse.posts().get(0).postId()).isEqualTo( + jejuSeptember17hourPostResponse.postId()); + assertThat(jeju17hourPostsSearchResponse.posts().get(1).postId()).isEqualTo( + jejuAugust17hourPostResponse.postId()); + + assertThat(hour17PostsSearchResponse.posts().get(0).postId()).isEqualTo( + seoulSeptember17hourPostResponse.postId()); + assertThat(hour17PostsSearchResponse.posts().get(1).postId()).isEqualTo( + jejuSeptember17hourPostResponse.postId()); + assertThat(hour17PostsSearchResponse.posts().get(2).postId()).isEqualTo(jejuAugust17hourPostResponse.postId()); + } + private PointResponse createPoint() { PointCreateRequest request = new PointCreateRequest( trip.id(), @@ -599,16 +681,16 @@ private PointResponse createPoint() { return response.as(PointResponse.class); } - private PostCreateResponse createPost() { + private PostCreateResponse createPost(String address, LocalDateTime localDateTime) { // given PostAndPointCreateRequest postAndPointCreateRequest = new PostAndPointCreateRequest( trip.id(), "우도의 바닷가", - "제주특별자치도 제주시 애월읍 소길리", + address, "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", 1.1, 2.2, - LocalDateTime.of(2023, 7, 18, 20, 24) + localDateTime ); MultiPartSpecification multiPartSpecification = new MultiPartSpecBuilder(postAndPointCreateRequest) @@ -626,11 +708,9 @@ private PostCreateResponse createPost() { .then().log().all() .extract(); - PostCreateResponse postResponse = createResponse.as(PostCreateResponse.class); - return postResponse; + return createResponse.as(PostCreateResponse.class); } - @Test PostResponse readPost(Long postId) { ExtractableResponse findResponse = RestAssured.given().log().all() .contentType(APPLICATION_JSON_VALUE) diff --git a/backend/src/test/java/dev/tripdraw/presentation/controller/ControllerTest.java b/backend/src/test/java/dev/tripdraw/test/ControllerTest.java similarity index 91% rename from backend/src/test/java/dev/tripdraw/presentation/controller/ControllerTest.java rename to backend/src/test/java/dev/tripdraw/test/ControllerTest.java index 328983b8b..49dba79f2 100644 --- a/backend/src/test/java/dev/tripdraw/presentation/controller/ControllerTest.java +++ b/backend/src/test/java/dev/tripdraw/test/ControllerTest.java @@ -1,4 +1,4 @@ -package dev.tripdraw.presentation.controller; +package dev.tripdraw.test; import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; @@ -16,7 +16,7 @@ public abstract class ControllerTest { private int port; @BeforeEach - void setUp() { + public void setUp() { RestAssured.port = port; } } diff --git a/backend/src/test/java/dev/tripdraw/application/ServiceTest.java b/backend/src/test/java/dev/tripdraw/test/ServiceTest.java similarity index 94% rename from backend/src/test/java/dev/tripdraw/application/ServiceTest.java rename to backend/src/test/java/dev/tripdraw/test/ServiceTest.java index 331e05767..a616a43b3 100644 --- a/backend/src/test/java/dev/tripdraw/application/ServiceTest.java +++ b/backend/src/test/java/dev/tripdraw/test/ServiceTest.java @@ -1,4 +1,4 @@ -package dev.tripdraw.application; +package dev.tripdraw.test; import jakarta.transaction.Transactional; import java.lang.annotation.ElementType; diff --git a/backend/src/test/java/dev/tripdraw/test/TestFixture.java b/backend/src/test/java/dev/tripdraw/test/TestFixture.java deleted file mode 100644 index 6c9f9dcca..000000000 --- a/backend/src/test/java/dev/tripdraw/test/TestFixture.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.tripdraw.test; - -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.oauth.OauthType; -import dev.tripdraw.domain.post.Post; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripName; -import dev.tripdraw.domain.trip.TripStatus; -import java.time.LocalDateTime; - -@SuppressWarnings("NonAsciiCharacters") -public class TestFixture { - - public static Member 사용자() { - return new Member(1L, "통후추", "", OauthType.KAKAO); - } - - public static Point 위치정보() { - return new Point(1L, 1.1, 2.2, false, LocalDateTime.now()); - } - - public static Trip 여행() { - return new Trip(1L, TripName.from("통후추"), 사용자(), TripStatus.ONGOING, "", ""); - } - - public static Post 감상() { - return new Post("감상 제목", 위치정보(), "주소", "감상", 사용자(), 1L); - } -} diff --git a/backend/src/test/java/dev/tripdraw/common/TestKakaoApiClient.java b/backend/src/test/java/dev/tripdraw/test/TestKakaoApiClient.java similarity index 54% rename from backend/src/test/java/dev/tripdraw/common/TestKakaoApiClient.java rename to backend/src/test/java/dev/tripdraw/test/TestKakaoApiClient.java index 8ad6c65de..e83a45038 100644 --- a/backend/src/test/java/dev/tripdraw/common/TestKakaoApiClient.java +++ b/backend/src/test/java/dev/tripdraw/test/TestKakaoApiClient.java @@ -1,10 +1,10 @@ -package dev.tripdraw.common; +package dev.tripdraw.test; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; -import dev.tripdraw.application.oauth.OauthClient; -import dev.tripdraw.domain.oauth.OauthType; -import dev.tripdraw.dto.auth.OauthInfo; +import dev.tripdraw.auth.dto.OauthInfo; +import dev.tripdraw.auth.oauth.OauthClient; +import dev.tripdraw.common.auth.OauthType; public class TestKakaoApiClient implements OauthClient { diff --git a/backend/src/test/java/dev/tripdraw/test/fixture/AuthFixture.java b/backend/src/test/java/dev/tripdraw/test/fixture/AuthFixture.java new file mode 100644 index 000000000..c94b35f3b --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/fixture/AuthFixture.java @@ -0,0 +1,32 @@ +package dev.tripdraw.test.fixture; + +import dev.tripdraw.auth.config.AccessTokenConfig; +import dev.tripdraw.auth.config.RefreshTokenConfig; + +public class AuthFixture { + + public static final String 유효하지_않은_토큰 = "Invalid.Token.XD"; + private static final long INVALID_TOKEN_EXPIRE_TIME = -180000L; + private static final String ACCESS_TOKEN_KEY = + "ACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKENACCESSTOKEN"; + private static final String REFRESH_TOKEN_KEY = + "REFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFRESHTOKENREFR"; + private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; + private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 14; + + public static AccessTokenConfig 테스트_ACCESS_TOKEN_설정() { + return new AccessTokenConfig(ACCESS_TOKEN_KEY, ACCESS_TOKEN_EXPIRE_TIME); + } + + public static AccessTokenConfig 만료된_토큰_생성용_ACCESS_TOKEN_설정() { + return new AccessTokenConfig(ACCESS_TOKEN_KEY, INVALID_TOKEN_EXPIRE_TIME); + } + + public static RefreshTokenConfig 테스트_REFRESH_TOKEN_설정() { + return new RefreshTokenConfig(REFRESH_TOKEN_KEY, REFRESH_TOKEN_EXPIRE_TIME); + } + + public static RefreshTokenConfig 만료된_토큰_생성용_REFRESH_TOKEN_설정() { + return new RefreshTokenConfig(REFRESH_TOKEN_KEY, INVALID_TOKEN_EXPIRE_TIME); + } +} diff --git a/backend/src/test/java/dev/tripdraw/test/fixture/MemberFixture.java b/backend/src/test/java/dev/tripdraw/test/fixture/MemberFixture.java new file mode 100644 index 000000000..4b051817e --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/fixture/MemberFixture.java @@ -0,0 +1,11 @@ +package dev.tripdraw.test.fixture; + +import dev.tripdraw.common.auth.OauthType; +import dev.tripdraw.member.domain.Member; + +public class MemberFixture { + + public static Member 사용자() { + return new Member(1L, "통후추", "", OauthType.KAKAO); + } +} diff --git a/backend/src/test/java/dev/tripdraw/test/fixture/TestFixture.java b/backend/src/test/java/dev/tripdraw/test/fixture/TestFixture.java new file mode 100644 index 000000000..8f5c37aed --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/fixture/TestFixture.java @@ -0,0 +1,124 @@ +package dev.tripdraw.test.fixture; + +import static dev.tripdraw.trip.domain.TripStatus.FINISHED; + +import dev.tripdraw.common.auth.OauthType; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.dto.PostAndPointCreateRequest; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripName; +import dev.tripdraw.trip.domain.TripStatus; +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.TripUpdateRequest; +import java.time.LocalDateTime; + +@SuppressWarnings("NonAsciiCharacters") +public class TestFixture { + + public static Member 사용자() { + return new Member(1L, "통후추", "", OauthType.KAKAO); + } + + public static Point 위치정보() { + return new Point(1L, 1.1, 2.2, false, LocalDateTime.now(), 여행()); + } + + public static Point 위치정보(int year, int month, int dayOfMonth, int hour, int minute) { + return new Point(1.1, 2.2, LocalDateTime.of(year, month, dayOfMonth, hour, minute)); + } + + public static Trip 여행() { + return new Trip(1L, TripName.from("통후추"), 사용자(), TripStatus.ONGOING, "", ""); + } + + public static Post 감상() { + return new Post("감상 제목", 위치정보(), "주소", "감상", 사용자(), 1L); + } + + public static PointCreateRequest pointCreateRequest(Long tripId) { + return new PointCreateRequest( + tripId, + 1.1, + 2.2, + LocalDateTime.of(2023, 7, 18, 20, 24) + ); + } + + public static TripUpdateRequest tripUpdateRequest() { + return new TripUpdateRequest("제주도 여행", FINISHED); + } + + public static PostAndPointCreateRequest postAndPointCreateRequest(Long tripId) { + return new PostAndPointCreateRequest( + tripId, + "우도의 바닷가", + "제주특별자치도 제주시 애월읍 소길리", + "우도에서 땅콩 아이스크림을 먹었다.\\n너무 맛있었다.", + 1.1, + 2.2, + LocalDateTime.of(2023, 7, 18, 20, 24) + ); + } + + public static PostAndPointCreateRequest 제주_2023_2_1_수(Long tripId) { + return new PostAndPointCreateRequest( + tripId, + "제목", + "제주특별자치도 제주시 애월읍", + "내용", + 0.0, + 0.0, + LocalDateTime.of(2023, 2, 1, 1, 1) + ); + } + + public static PostAndPointCreateRequest 서울_2023_1_1_일(Long tripId) { + return new PostAndPointCreateRequest( + tripId, + "제목", + "seoul_songpa_sincheon", + "내용", + 0.0, + 0.0, + LocalDateTime.of(2023, 1, 1, 10, 1) + ); + } + + public static PostAndPointCreateRequest 제주_2023_1_1_일(Long tripId) { + return new PostAndPointCreateRequest( + tripId, + "제목", + "제주특별자치도 제주시 애월읍", + "내용", + 0.0, + 0.0, + LocalDateTime.of(2023, 1, 1, 1, 1) + ); + } + + public static PostAndPointCreateRequest 서울_2022_1_2_일(Long tripId) { + return new PostAndPointCreateRequest( + tripId, + "제목", + "seoul_songpa_Bangi", + "내용", + 0.0, + 0.0, + LocalDateTime.of(2022, 1, 2, 1, 1) + ); + } + + public static PostAndPointCreateRequest 양양_2021_3_2_화(Long tripId) { + return new PostAndPointCreateRequest( + tripId, + "제목", + "강원도 양양", + "내용", + 0.0, + 0.0, + LocalDateTime.of(2021, 3, 2, 10, 1) + ); + } +} diff --git a/backend/src/test/java/dev/tripdraw/test/fixture/TripSearchConditionsFixture.java b/backend/src/test/java/dev/tripdraw/test/fixture/TripSearchConditionsFixture.java new file mode 100644 index 000000000..9cbcdf4e7 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/fixture/TripSearchConditionsFixture.java @@ -0,0 +1,49 @@ +package dev.tripdraw.test.fixture; + +import dev.tripdraw.trip.dto.TripSearchConditions; + +import java.util.Set; + +public class TripSearchConditionsFixture { + + public static TripSearchConditions emptyTripSearchConditions() { + return TripSearchConditions.builder() + .build(); + } + + public static TripSearchConditions yearsTripSearchConditions(Set years) { + return TripSearchConditions.builder() + .years(years) + .build(); + } + + public static TripSearchConditions monthsTripSearchConditions(Set months) { + return TripSearchConditions.builder() + .months(months) + .build(); + } + + public static TripSearchConditions daysOfWeekTripSearchConditions(Set daysOfWeek) { + return TripSearchConditions.builder() + .daysOfWeek(daysOfWeek) + .build(); + } + + public static TripSearchConditions ageRangesTripSearchConditions(Set ageRanges) { + return TripSearchConditions.builder() + .ageRanges(ageRanges) + .build(); + } + + public static TripSearchConditions gendersTripSearchConditions(Set genders) { + return TripSearchConditions.builder() + .genders(genders) + .build(); + } + + public static TripSearchConditions addressTripSearchConditions(String address) { + return TripSearchConditions.builder() + .address(address) + .build(); + } +} diff --git a/backend/src/test/java/dev/tripdraw/test/fixture/TripSearchQueryParamsFixture.java b/backend/src/test/java/dev/tripdraw/test/fixture/TripSearchQueryParamsFixture.java new file mode 100644 index 000000000..cf849b5cf --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/fixture/TripSearchQueryParamsFixture.java @@ -0,0 +1,60 @@ +package dev.tripdraw.test.fixture; + +import java.util.Map; +import java.util.Set; + +public class TripSearchQueryParamsFixture { + + public static Map limitParams(int limit) { + return Map.of("limit", limit); + } + + public static Map lastViewedIdAndLimitParams(Long lastViewedId, int limit) { + return Map.of( + "lastViewedId", lastViewedId, + "limit", limit + ); + } + + public static Map yearsAndLimitParams(Set years, int limit) { + return Map.of( + "years", years, + "limit", limit + ); + } + + public static Map monthsAndLimitParams(Set months, int limit) { + return Map.of( + "months", months, + "limit", limit + ); + } + + public static Map daysOfWeekAndLimitParams(Set daysOfWeek, int limit) { + return Map.of( + "daysOfWeek", daysOfWeek, + "limit", limit + ); + } + + public static Map ageRangesAndLimitParams(Set ageRanges, int limit) { + return Map.of( + "ageRanges", ageRanges, + "limit", limit + ); + } + + public static Map gendersAndLimitParams(Set genders, int limit) { + return Map.of( + "genders", genders, + "limit", limit + ); + } + + public static Map addressAndLimitParams(String address, int limit) { + return Map.of( + "address", address, + "limit", limit + ); + } +} diff --git a/backend/src/test/java/dev/tripdraw/test/step/PostStep.java b/backend/src/test/java/dev/tripdraw/test/step/PostStep.java new file mode 100644 index 000000000..b75b76fbf --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/step/PostStep.java @@ -0,0 +1,30 @@ +package dev.tripdraw.test.step; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + +import dev.tripdraw.post.dto.PostAndPointCreateRequest; +import dev.tripdraw.post.dto.PostCreateResponse; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +public class PostStep { + + public static ExtractableResponse createPostAtCurrentPoint(PostAndPointCreateRequest request, + String token) { + return RestAssured.given().log().all() + .contentType(MULTIPART_FORM_DATA_VALUE) + .auth().preemptive().oauth2(token) + .multiPart("dto", request, APPLICATION_JSON_VALUE) + .when().post("/posts/current-location") + .then().log().all() + .extract(); + } + + public static PostCreateResponse createPostAtCurrentPointAndGetResponse(PostAndPointCreateRequest request, + String token) { + ExtractableResponse response = createPostAtCurrentPoint(request, token); + return response.as(PostCreateResponse.class); + } +} diff --git a/backend/src/test/java/dev/tripdraw/test/step/TripStep.java b/backend/src/test/java/dev/tripdraw/test/step/TripStep.java new file mode 100644 index 000000000..075ce1565 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/test/step/TripStep.java @@ -0,0 +1,62 @@ +package dev.tripdraw.test.step; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.PointResponse; +import dev.tripdraw.trip.dto.TripCreateResponse; +import dev.tripdraw.trip.dto.TripResponse; +import dev.tripdraw.trip.dto.TripUpdateRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +public class TripStep { + + public static ExtractableResponse createTrip(String token) { + return RestAssured.given().log().all() + .auth().preemptive().oauth2(token) + .when().post("/trips") + .then().log().all() + .extract(); + } + + public static TripCreateResponse createTripAndGetResponse(String token) { + ExtractableResponse response = createTrip(token); + return response.as(TripCreateResponse.class); + } + + public static ExtractableResponse addPoint(PointCreateRequest request, String token) { + return RestAssured.given().log().all() + .contentType(APPLICATION_JSON_VALUE) + .auth().preemptive().oauth2(token) + .body(request) + .when().post("/points") + .then().log().all() + .extract(); + } + + public static PointResponse addPointAndGetResponse(PointCreateRequest request, String token) { + ExtractableResponse response = addPoint(request, token); + return response.as(PointResponse.class); + } + + public static ExtractableResponse updateTrip(TripUpdateRequest request, Long tripId, String token) { + return RestAssured.given().log().all() + .contentType(APPLICATION_JSON_VALUE) + .auth().preemptive().oauth2(token) + .body(request) + .when().patch("/trips/{tripId}", tripId) + .then().log().all() + .extract(); + } + + public static TripResponse searchTripAndGetResponse(Long tripId, String token) { + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(token) + .when().get("/trips/{tripId}", tripId) + .then().log().all() + .extract(); + return response.as(TripResponse.class); + } +} diff --git a/backend/src/test/java/dev/tripdraw/trip/acceptance/TripSearchAcceptanceTest.java b/backend/src/test/java/dev/tripdraw/trip/acceptance/TripSearchAcceptanceTest.java new file mode 100644 index 000000000..1d95b8776 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/trip/acceptance/TripSearchAcceptanceTest.java @@ -0,0 +1,447 @@ +package dev.tripdraw.trip.acceptance; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.test.fixture.TestFixture.서울_2022_1_2_일; +import static dev.tripdraw.test.fixture.TestFixture.서울_2023_1_1_일; +import static dev.tripdraw.test.fixture.TestFixture.양양_2021_3_2_화; +import static dev.tripdraw.test.fixture.TestFixture.제주_2023_1_1_일; +import static dev.tripdraw.test.fixture.TestFixture.제주_2023_2_1_수; +import static dev.tripdraw.test.fixture.TripSearchQueryParamsFixture.addressAndLimitParams; +import static dev.tripdraw.test.fixture.TripSearchQueryParamsFixture.daysOfWeekAndLimitParams; +import static dev.tripdraw.test.fixture.TripSearchQueryParamsFixture.lastViewedIdAndLimitParams; +import static dev.tripdraw.test.fixture.TripSearchQueryParamsFixture.limitParams; +import static dev.tripdraw.test.fixture.TripSearchQueryParamsFixture.monthsAndLimitParams; +import static dev.tripdraw.test.fixture.TripSearchQueryParamsFixture.yearsAndLimitParams; +import static dev.tripdraw.test.step.PostStep.createPostAtCurrentPoint; +import static dev.tripdraw.test.step.TripStep.createTripAndGetResponse; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +import dev.tripdraw.auth.application.JwtTokenProvider; +import dev.tripdraw.draw.application.RouteImageGenerator; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.test.ControllerTest; +import dev.tripdraw.trip.dto.TripSearchResponse; +import dev.tripdraw.trip.dto.TripsSearchResponse; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +public class TripSearchAcceptanceTest extends ControllerTest { + + private static final String WRONG_TOKEN = "wrong.long.token"; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @MockBean + private RouteImageGenerator routeImageGenerator; + + private String huchuToken; + private Long lastViewedId; + private Long 후추_양양_2021_3_2_화; + private Long 후추_서울_2022_1_2_일; + private Long 리오_제주_2023_1_1_일; + private Long 리오_서울_2023_1_1_일; + private Long 허브_제주_2023_2_1_수; + + @BeforeEach + public void setUp() { + super.setUp(); + + Member huchu = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + Member reo = memberRepository.save(new Member("리오", "kakaoId", KAKAO)); + Member herb = memberRepository.save(new Member("허브", "kakaoId", KAKAO)); + + huchuToken = jwtTokenProvider.generateAccessToken(huchu.id().toString()); + String reoToken = jwtTokenProvider.generateAccessToken(reo.id().toString()); + String herbToken = jwtTokenProvider.generateAccessToken(herb.id().toString()); + + 후추_양양_2021_3_2_화 = createTripAndGetResponse(huchuToken).tripId(); + createPostAtCurrentPoint(양양_2021_3_2_화(후추_양양_2021_3_2_화), huchuToken); + + 후추_서울_2022_1_2_일 = createTripAndGetResponse(huchuToken).tripId(); + createPostAtCurrentPoint(서울_2022_1_2_일(후추_서울_2022_1_2_일), huchuToken); + + 리오_제주_2023_1_1_일 = createTripAndGetResponse(reoToken).tripId(); + createPostAtCurrentPoint(제주_2023_1_1_일(리오_제주_2023_1_1_일), reoToken); + + 리오_서울_2023_1_1_일 = createTripAndGetResponse(reoToken).tripId(); + createPostAtCurrentPoint(서울_2023_1_1_일(리오_서울_2023_1_1_일), reoToken); + + 허브_제주_2023_2_1_수 = createTripAndGetResponse(herbToken).tripId(); + createPostAtCurrentPoint(제주_2023_2_1_수(허브_제주_2023_2_1_수), herbToken); + + lastViewedId = 허브_제주_2023_2_1_수; + } + + + @Nested + class 감상이_있는_모든_여행을_페이지네이션으로_조회할_때 { + + @Nested + class 개수_제한을 { + + @ParameterizedTest + @CsvSource({"1, 1", "4, 4"}) + void 조회할_수_있는_여행_수_미만으로_입력하면_해당_개수만큼_여행이_조회된다(int limit, int expectedSize) { + // given + var tripSearchParams = limitParams(limit); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(tripsSearchResponse.trips()).hasSize(expectedSize); + }); + } + + @ParameterizedTest + @ValueSource(ints = {1, 4}) + void 조회할_수_있는_여행_수_미만으로_입력하면_다음_페이지가_존재한다(int limit) { + // given + var tripSearchParams = limitParams(limit); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(tripsSearchResponse.hasNextPage()).isTrue(); + }); + } + + @ParameterizedTest + @CsvSource({"5, 5", "6, 5"}) + void 조회할_수_있는_여행_수_이상으로_입력하면_모든_여행이_조회된다(int limit, int expectedSize) { + // given + var tripSearchParams = limitParams(limit); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(tripsSearchResponse.trips()).hasSize(expectedSize); + }); + } + + @ParameterizedTest + @ValueSource(ints = {5, 6}) + void 조회할_수_있는_여행_수_이상으로_입력하면_다음_페이지가_존재하지_않는다(int limit) { + // given + var tripSearchParams = limitParams(limit); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(tripsSearchResponse.hasNextPage()).isFalse(); + }); + } + } + + @Nested + class 마지막으로_조회한_여행_ID를 { + + private static final int LIMIT = 10; + + @Test + void 입력하지_않으면_최신_여행부터_내림차순으로_조회된다() { + // given + var tripSearchParams = limitParams(LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 허브_제주_2023_2_1_수, + 리오_서울_2023_1_1_일, + 리오_제주_2023_1_1_일, + 후추_서울_2022_1_2_일, + 후추_양양_2021_3_2_화 + ); + }); + } + + @Test + void 입력하면_해당_여행_이하로_최신_여행을_내림차순으로_조회된다() { + // given + var tripSearchParams = lastViewedIdAndLimitParams(lastViewedId, LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 리오_서울_2023_1_1_일, + 리오_제주_2023_1_1_일, + 후추_서울_2022_1_2_일, + 후추_양양_2021_3_2_화 + ); + }); + } + } + } + + @Nested + class 감상이_있는_모든_여행을_조건에_따라_조회할_때 { + + private static final int LIMIT = 10; + + @Test + void 조건없이_조회할_수_있다() { + // given + var tripSearchParams = limitParams(LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 허브_제주_2023_2_1_수, + 리오_서울_2023_1_1_일, + 리오_제주_2023_1_1_일, + 후추_서울_2022_1_2_일, + 후추_양양_2021_3_2_화 + ); + }); + } + + @Test + void 연도를_조건으로_조회할_수_있다() { + // given + var tripSearchParams = yearsAndLimitParams(Set.of(2023), LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 허브_제주_2023_2_1_수, + 리오_서울_2023_1_1_일, + 리오_제주_2023_1_1_일 + ); + }); + } + + @Test + void 월을_조건으로_조회할_수_있다() { + // given + var tripSearchParams = monthsAndLimitParams(Set.of(1, 2), LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 허브_제주_2023_2_1_수, + 리오_서울_2023_1_1_일, + 리오_제주_2023_1_1_일, + 후추_서울_2022_1_2_일 + ); + }); + } + + @Test + void 요일을_조건으로_조회할_수_있다() { + // given + var tripSearchParams = daysOfWeekAndLimitParams(Set.of(1, 3), LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 리오_서울_2023_1_1_일, + 리오_제주_2023_1_1_일, + 후추_서울_2022_1_2_일, + 후추_양양_2021_3_2_화 + ); + }); + } + + @Test + void 주소를_조건으로_조회할_수_있다() { + // given + var tripSearchParams = addressAndLimitParams("seoul_songpa", LIMIT); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 리오_서울_2023_1_1_일, + 후추_서울_2022_1_2_일 + ); + }); + } + + @Test + void 여러_조건으로_조회할_수_있다() { + // given + Map params = Map.of( + "years", Set.of(2023, 2021), + "daysOfWeek", Set.of(1), + "address", "seoul", + "limit", LIMIT + ); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().preemptive().oauth2(huchuToken) + .params(params) + .when().get("/trips") + .then().log().all() + .extract(); + + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 리오_서울_2023_1_1_일 + ); + }); + } + + @Test + void 인증에_실패할_경우_예외를_발생시킨다() { + // given + var tripSearchParams = limitParams(LIMIT); + + // expect + RestAssured.given().log().all() + .auth().preemptive().oauth2(WRONG_TOKEN) + .params(tripSearchParams) + .when().get("/trips") + .then().log().all() + .statusCode(UNAUTHORIZED.value()); + } + } + + private List searchedTripIds(TripsSearchResponse tripsSearchResponse) { + return tripsSearchResponse.trips().stream() + .map(TripSearchResponse::tripId) + .toList(); + } +} diff --git a/backend/src/test/java/dev/tripdraw/trip/application/TripDeleteEventHandlerTest.java b/backend/src/test/java/dev/tripdraw/trip/application/TripDeleteEventHandlerTest.java new file mode 100644 index 000000000..5ac2d23bf --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/trip/application/TripDeleteEventHandlerTest.java @@ -0,0 +1,40 @@ +package dev.tripdraw.trip.application; + +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +import dev.tripdraw.member.domain.MemberDeleteEvent; +import dev.tripdraw.trip.domain.TripRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@ExtendWith(MockitoExtension.class) +class TripDeleteEventHandlerTest { + + @Mock + private TripRepository tripRepository; + + @InjectMocks + private TripDeleteEventHandler tripDeleteEventHandler; + + @Test + void 회원_삭제_이벤트를_받아_회원의_여행을_삭제한다() { + // given + MemberDeleteEvent memberDeleteEvent = new MemberDeleteEvent(1L); + + // when + tripDeleteEventHandler.deletePostByMemberId(memberDeleteEvent); + + // then + then(tripRepository) + .should(times(1)) + .deleteByMemberId(1L); + } +} diff --git a/backend/src/test/java/dev/tripdraw/trip/application/TripQueryServiceTest.java b/backend/src/test/java/dev/tripdraw/trip/application/TripQueryServiceTest.java new file mode 100644 index 000000000..aea4bbf77 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/trip/application/TripQueryServiceTest.java @@ -0,0 +1,53 @@ +package dev.tripdraw.trip.application; + +import dev.tripdraw.test.fixture.TripSearchConditionsFixture; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.dto.TripSearchConditions; +import dev.tripdraw.trip.query.TripCustomRepository; +import dev.tripdraw.trip.query.TripPaging; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@ExtendWith(MockitoExtension.class) +class TripQueryServiceTest { + + @Mock + private TripCustomRepository tripCustomRepository; + + @InjectMocks + private TripQueryService tripQueryService; + + @Test + void 쿼리_조건에_따라_여행을_조회한다() { + // given + TripSearchConditions tripSearchConditions = TripSearchConditionsFixture.emptyTripSearchConditions(); + TripPaging tripPaging = new TripPaging(1L, 10); + + given(tripCustomRepository.findAllByConditions(tripSearchConditions, tripPaging)) + .willReturn(new ArrayList<>()); + + // when + List trips = tripQueryService.readAllByQueryConditions(tripSearchConditions, tripPaging); + + // then + assertThat(trips).isEmpty(); + then(tripCustomRepository) + .should(times(1)) + .findAllByConditions(tripSearchConditions, tripPaging); + } +} diff --git a/backend/src/test/java/dev/tripdraw/application/TripServiceTest.java b/backend/src/test/java/dev/tripdraw/trip/application/TripServiceTest.java similarity index 65% rename from backend/src/test/java/dev/tripdraw/application/TripServiceTest.java rename to backend/src/test/java/dev/tripdraw/trip/application/TripServiceTest.java index 2a656cfc8..24a907a3f 100644 --- a/backend/src/test/java/dev/tripdraw/application/TripServiceTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/application/TripServiceTest.java @@ -1,32 +1,36 @@ -package dev.tripdraw.application; - -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.domain.trip.TripStatus.FINISHED; -import static dev.tripdraw.exception.member.MemberExceptionType.MEMBER_NOT_FOUND; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_DELETED; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_IN_TRIP; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_NOT_FOUND; +package dev.tripdraw.trip.application; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.member.exception.MemberExceptionType.MEMBER_NOT_FOUND; +import static dev.tripdraw.trip.domain.TripStatus.FINISHED; +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import dev.tripdraw.application.draw.RouteImageGenerator; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.trip.Point; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.auth.LoginUser; -import dev.tripdraw.dto.trip.PointCreateRequest; -import dev.tripdraw.dto.trip.PointCreateResponse; -import dev.tripdraw.dto.trip.PointResponse; -import dev.tripdraw.dto.trip.TripCreateResponse; -import dev.tripdraw.dto.trip.TripResponse; -import dev.tripdraw.dto.trip.TripSearchResponse; -import dev.tripdraw.dto.trip.TripUpdateRequest; -import dev.tripdraw.dto.trip.TripsSearchResponse; -import dev.tripdraw.exception.member.MemberException; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.common.auth.LoginUser; +import dev.tripdraw.draw.application.RouteImageGenerator; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.member.exception.MemberException; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.test.ServiceTest; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.PointCreateResponse; +import dev.tripdraw.trip.dto.PointResponse; +import dev.tripdraw.trip.dto.TripCreateResponse; +import dev.tripdraw.trip.dto.TripResponse; +import dev.tripdraw.trip.dto.TripSearchRequest; +import dev.tripdraw.trip.dto.TripSearchResponse; +import dev.tripdraw.trip.dto.TripSearchResponseOfMember; +import dev.tripdraw.trip.dto.TripUpdateRequest; +import dev.tripdraw.trip.dto.TripsSearchResponse; +import dev.tripdraw.trip.dto.TripsSearchResponseOfMember; +import dev.tripdraw.trip.exception.TripException; import java.time.LocalDateTime; import java.util.List; import java.util.Objects; @@ -47,6 +51,9 @@ class TripServiceTest { @Autowired private MemberRepository memberRepository; + @Autowired + private PostRepository postRepository; + @MockBean private RouteImageGenerator routeImageGenerator; @@ -56,8 +63,12 @@ class TripServiceTest { @BeforeEach void setUp() { Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - trip = tripRepository.save(Trip.from(member)); + Trip trip = Trip.from(member); + Point point = new Point(3.14, 5.25, LocalDateTime.now()); + trip.add(point); + this.trip = tripRepository.save(trip); loginUser = new LoginUser(member.id()); + postRepository.save(new Post("", point, "제주특별자치도 제주시 애월읍", "", member, trip.id())); } @Test @@ -117,13 +128,11 @@ void setUp() { tripService.deletePoint(loginUser, response.pointId(), trip.id()); // then - Point deletedPoint = trip.route().points() + boolean expected = trip.route().points() .stream() - .filter(point -> Objects.equals(point.id(), response.pointId())) - .findFirst() - .get(); + .anyMatch(point -> Objects.equals(point.id(), response.pointId())); - assertThat(deletedPoint.isDeleted()).isTrue(); + assertThat(expected).isTrue(); } @Test @@ -140,36 +149,32 @@ void setUp() { } @Test - void 여행에서_위치정보를_삭제시_여행에_해당_위치정보가_존재하지_않으면_예외를_발생시킨다() { - // given - PointCreateRequest pointCreateRequest = new PointCreateRequest(trip.id(), 1.1, 2.2, LocalDateTime.now()); - tripService.addPoint(loginUser, pointCreateRequest); - - Point inExistentPoint = new Point(Long.MAX_VALUE, 1.1, 2.2, false, LocalDateTime.now()); + void 특정_회원의_전체_여행을_조회한다() { + // given & when + TripsSearchResponseOfMember tripsSearchResponseOfMember = tripService.readAllTripsOf(loginUser); - // expect - assertThatThrownBy(() -> tripService.deletePoint(loginUser, inExistentPoint.id(), trip.id())) - .isInstanceOf(TripException.class) - .hasMessage(POINT_NOT_IN_TRIP.message()); + // then + assertThat(tripsSearchResponseOfMember).usingRecursiveComparison().isEqualTo( + new TripsSearchResponseOfMember(List.of( + new TripSearchResponseOfMember( + trip.id(), + trip.nameValue(), + trip.imageUrl(), + trip.routeImageUrl() + ) + )) + ); } @Test - void 여행에서_위치정보를_삭제시_이미_삭제된_위치정보면_예외를_발생시킨다() { + void 모든_회원의_감상이_있는_여행_전체를_조회한다() { // given - PointCreateRequest pointCreateRequest = new PointCreateRequest(trip.id(), 1.1, 2.2, LocalDateTime.now()); - PointCreateResponse response = tripService.addPoint(loginUser, pointCreateRequest); - tripService.deletePoint(loginUser, response.pointId(), trip.id()); - - // expect - assertThatThrownBy(() -> tripService.deletePoint(loginUser, response.pointId(), trip.id())) - .isInstanceOf(TripException.class) - .hasMessage(POINT_ALREADY_DELETED.message()); - } + TripSearchRequest tripSearchRequest = TripSearchRequest.builder() + .limit(10) + .build(); - @Test - void 전체_여행을_조회한다() { - // given & when - TripsSearchResponse tripsSearchResponse = tripService.readAllTrips(loginUser); + // when + TripsSearchResponse tripsSearchResponse = tripService.readAll(tripSearchRequest); // then assertThat(tripsSearchResponse).usingRecursiveComparison().isEqualTo( @@ -178,9 +183,12 @@ void setUp() { trip.id(), trip.nameValue(), trip.imageUrl(), - trip.routeImageUrl() - ) - )) + trip.routeImageUrl(), + trip.createdAt(), + trip.updatedAt() + )), + false + ) ); } diff --git a/backend/src/test/java/dev/tripdraw/trip/domain/PointRepositoryTest.java b/backend/src/test/java/dev/tripdraw/trip/domain/PointRepositoryTest.java new file mode 100644 index 000000000..541138ac7 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/trip/domain/PointRepositoryTest.java @@ -0,0 +1,69 @@ +package dev.tripdraw.trip.domain; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.trip.exception.TripExceptionType.POINT_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import dev.tripdraw.common.config.JpaConfig; +import dev.tripdraw.common.config.QueryDslConfig; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.trip.exception.TripException; +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@DataJpaTest +@Import({JpaConfig.class, QueryDslConfig.class}) +class PointRepositoryTest { + + @Autowired + private PointRepository pointRepository; + + @Autowired + private TripRepository tripRepository; + + @Autowired + private MemberRepository memberRepository; + + private Trip trip; + + @BeforeEach + void setUp() { + Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + trip = tripRepository.save(Trip.from(member)); + } + + @Test + void 위치정보_ID로_위치정보를_조회한다() { + // given + Point point = new Point(3.14, 5.25, LocalDateTime.now()); + point.setTrip(trip); + pointRepository.save(point); + + // when + Point foundPoint = pointRepository.getById(point.id()); + + // then + assertThat(foundPoint).isEqualTo(point); + } + + @Test + void 위치정보_ID로_위치정보를_조회할_때_존재하지_않는_경우_예외를_발생시킨다() { + // given + Long wrongId = Long.MIN_VALUE; + + // expect + assertThatThrownBy(() -> pointRepository.getById(wrongId)) + .isInstanceOf(TripException.class) + .hasMessage(POINT_NOT_FOUND.message()); + } +} diff --git a/backend/src/test/java/dev/tripdraw/domain/trip/PointTest.java b/backend/src/test/java/dev/tripdraw/trip/domain/PointTest.java similarity index 59% rename from backend/src/test/java/dev/tripdraw/domain/trip/PointTest.java rename to backend/src/test/java/dev/tripdraw/trip/domain/PointTest.java index 9b2d613c6..ac22e44ff 100644 --- a/backend/src/test/java/dev/tripdraw/domain/trip/PointTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/domain/PointTest.java @@ -1,11 +1,14 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_DELETED; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_HAS_POST; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.trip.exception.TripExceptionType.POINT_ALREADY_HAS_POST; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.trip.exception.TripException; +import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -15,50 +18,43 @@ class PointTest { @Test - void 위치정보를_삭제한다() { + void 위치에_감상을_등록한다() { // given Point point = new Point(); // when - point.delete(); + point.registerPost(); // then - assertThat(point.isDeleted()).isTrue(); + assertThat(point.hasPost()).isTrue(); } @Test - void 이미_삭제된_위치정보를_삭제하면_예외를_발생시킨다() { + void 위치에_감상을_등록할_때_이미_감상이_등록되어_있으면_예외가_발생한다() { // given Point point = new Point(); - point.delete(); + point.registerPost(); // expect - assertThatThrownBy(point::delete) + assertThatThrownBy(point::registerPost) .isInstanceOf(TripException.class) - .hasMessage(POINT_ALREADY_DELETED.message()); + .hasMessage(POINT_ALREADY_HAS_POST.message()); } @Test - void 위치에_감상을_등록한다() { + void 여행을_등록한다() { // given - Point point = new Point(); + Point point = new Point(3.14, 5.25, LocalDateTime.now()); + Member member = new Member("통후추", "kakaoId", KAKAO); + Trip trip = Trip.from(member); // when - point.registerPost(); + point.setTrip(trip); // then - assertThat(point.hasPost()).isTrue(); - } - - @Test - void 위치에_감상을_등록할_때_이미_감상이_등록되어_있으면_예외가_발생한다() { - // given - Point point = new Point(); - point.registerPost(); - - // expect - assertThatThrownBy(point::registerPost) - .isInstanceOf(TripException.class) - .hasMessage(POINT_ALREADY_HAS_POST.message()); + assertSoftly(softly -> { + softly.assertThat(point.trip()).isEqualTo(trip); + softly.assertThat(trip.route().points()).contains(point); + }); } } diff --git a/backend/src/test/java/dev/tripdraw/trip/domain/RouteTest.java b/backend/src/test/java/dev/tripdraw/trip/domain/RouteTest.java new file mode 100644 index 000000000..3504b57de --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/trip/domain/RouteTest.java @@ -0,0 +1,53 @@ +package dev.tripdraw.trip.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(ReplaceUnderscores.class) +class RouteTest { + + @Test + void 경로에_위치정보를_추가한다() { + // given + Route route = new Route(); + Point point = new Point(1.1, 2.2, LocalDateTime.now()); + + // when + route.add(point); + + // then + assertThat(route.points()).hasSize(1); + } + + @Nested + class 위치정보_포함_여부를_확인할_때 { + + @Test + void 위치정보_포함하면_참값을_반환한다() { + // given + Route route = new Route(); + Point point = new Point(1.1, 2.2, LocalDateTime.now()); + route.add(point); + + // expect + assertThat(route.contains(point)).isTrue(); + } + + @Test + void 위치정보를_포함하지_않으면_참값을_반환하지_않는다() { + // given + Route route = new Route(); + Point point = new Point(1.1, 2.2, LocalDateTime.now()); + + // expect + assertThat(route.contains(point)).isFalse(); + } + } +} + diff --git a/backend/src/test/java/dev/tripdraw/domain/trip/TripNameTest.java b/backend/src/test/java/dev/tripdraw/trip/domain/TripNameTest.java similarity index 93% rename from backend/src/test/java/dev/tripdraw/domain/trip/TripNameTest.java rename to backend/src/test/java/dev/tripdraw/trip/domain/TripNameTest.java index f1eca3f27..d4d68a0a7 100644 --- a/backend/src/test/java/dev/tripdraw/domain/trip/TripNameTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/domain/TripNameTest.java @@ -1,4 +1,4 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; import static org.assertj.core.api.Assertions.assertThat; @@ -17,7 +17,7 @@ class TripNameTest { // when tripName.change("제주도 여행"); - + // then assertThat(tripName.name()).isEqualTo("제주도 여행"); } diff --git a/backend/src/test/java/dev/tripdraw/domain/trip/TripRepositoryTest.java b/backend/src/test/java/dev/tripdraw/trip/domain/TripRepositoryTest.java similarity index 67% rename from backend/src/test/java/dev/tripdraw/domain/trip/TripRepositoryTest.java rename to backend/src/test/java/dev/tripdraw/trip/domain/TripRepositoryTest.java index f6bd8d274..f38e26b4c 100644 --- a/backend/src/test/java/dev/tripdraw/domain/trip/TripRepositoryTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/domain/TripRepositoryTest.java @@ -1,11 +1,16 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; +import dev.tripdraw.common.config.JpaConfig; +import dev.tripdraw.common.config.QueryDslConfig; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.trip.exception.TripException; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -14,9 +19,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@Import({JpaConfig.class, QueryDslConfig.class}) @DataJpaTest class TripRepositoryTest { @@ -55,7 +62,7 @@ void setUp() { List trips = tripRepository.findAllByMemberId(member.id()); // then - assertThat(trips).hasSize(0); + assertThat(trips).isEmpty(); } @Test @@ -90,4 +97,27 @@ void setUp() { // then assertThat(tripRepository.findById(trip.id())).isEmpty(); } + + @Test + void 여행_ID로_여행을_조회한다() { + // given + Trip trip = tripRepository.save(new Trip(TripName.from("제주도 여행"), member)); + + // when + Trip foundTrip = tripRepository.getById(trip.id()); + + // then + assertThat(foundTrip).isEqualTo(trip); + } + + @Test + void 여행_ID로_여행을_조회할_때_존재하지_않는_경우_예외를_발생시킨다() { + // given + Long wrongId = Long.MIN_VALUE; + + // expect + assertThatThrownBy(() -> tripRepository.getById(wrongId)) + .isInstanceOf(TripException.class) + .hasMessage(TRIP_NOT_FOUND.message()); + } } diff --git a/backend/src/test/java/dev/tripdraw/domain/trip/TripTest.java b/backend/src/test/java/dev/tripdraw/trip/domain/TripTest.java similarity index 66% rename from backend/src/test/java/dev/tripdraw/domain/trip/TripTest.java rename to backend/src/test/java/dev/tripdraw/trip/domain/TripTest.java index a6e623bcb..5bfa29998 100644 --- a/backend/src/test/java/dev/tripdraw/domain/trip/TripTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/domain/TripTest.java @@ -1,20 +1,20 @@ -package dev.tripdraw.domain.trip; +package dev.tripdraw.trip.domain; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.exception.trip.TripExceptionType.NOT_AUTHORIZED_TO_TRIP; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_ALREADY_DELETED; -import static dev.tripdraw.exception.trip.TripExceptionType.POINT_NOT_IN_TRIP; -import static dev.tripdraw.exception.trip.TripExceptionType.TRIP_INVALID_STATUS; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.trip.exception.TripExceptionType.NOT_AUTHORIZED_TO_TRIP; +import static dev.tripdraw.trip.exception.TripExceptionType.TRIP_INVALID_STATUS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.exception.trip.TripException; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.trip.exception.TripException; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -35,7 +35,37 @@ class TripTest { // then Route route = trip.route(); - assertThat(route.points()).hasSize(1); + assertSoftly(softly -> { + softly.assertThat(route.points()).hasSize(1); + softly.assertThat(point.trip()).isEqualTo(trip); + }); + } + + @Nested + class 위치정보_포함_여부를_확인할_때 { + + @Test + void 위치정보를_포함하는_여행이면_참값을_반환한다() { + // given + Member member = new Member("통후추", "kakaoId", KAKAO); + Trip trip = Trip.from(member); + Point point = new Point(1.1, 2.2, LocalDateTime.now()); + trip.add(point); + + // expect + assertThat(trip.contains(point)).isTrue(); + } + + @Test + void 위치정보를_포함하지_않는_여행이면_참값을_반환하지_않는다() { + // given + Member member = new Member("통후추", "kakaoId", KAKAO); + Trip trip = Trip.from(member); + Point point = new Point(1.1, 2.2, LocalDateTime.now()); + + // expect + assertThat(trip.contains(point)).isFalse(); + } } @Test @@ -126,54 +156,6 @@ class TripTest { .hasMessage(TRIP_INVALID_STATUS.message()); } - @Test - void 여행에_존재하는_위치정보를_삭제한다() { - // given - Member member = new Member("통후추", "kakaoId", KAKAO); - Trip trip = Trip.from(member); - Point point1 = new Point(1.1, 2.2, LocalDateTime.now()); - Point point2 = new Point(3.3, 4.4, LocalDateTime.now()); - trip.add(point1); - trip.add(point2); - Long point1Id = point1.id(); - - // when - trip.deletePointById(point1Id); - - // then - assertThat(point1.isDeleted()).isTrue(); - } - - @Test - void 삭제된_위치정보를_삭제하면_예외를_발생시킨다() { - // given - Member member = new Member("통후추", "kakaoId", KAKAO); - Trip trip = Trip.from(member); - Point point1 = new Point(1L, 1.1, 2.2, false, LocalDateTime.now()); - trip.add(point1); - trip.deletePointById(point1.id()); - - // expect - assertThatThrownBy(() -> trip.deletePointById(point1.id())) - .isInstanceOf(TripException.class) - .hasMessage(POINT_ALREADY_DELETED.message()); - } - - @Test - void 여행에_존재하지_않는_위치정보를_삭제하면_예외를_발생시킨다() { - // given - Member member = new Member("통후추", "kakaoId", KAKAO); - Trip trip = Trip.from(member); - Point point1 = new Point(1L, 1.1, 2.2, false, LocalDateTime.now()); - Point point2 = new Point(2L, 3.3, 4.4, false, LocalDateTime.now()); - trip.add(point1); - - // expect - assertThatThrownBy(() -> trip.deletePointById(point2.id())) - .isInstanceOf(TripException.class) - .hasMessage(POINT_NOT_IN_TRIP.message()); - } - @Test void 감상_사진_URL을_변경한다() { // given @@ -237,9 +219,9 @@ class TripTest { // given Member member = new Member("통후추", "kakaoId", KAKAO); Trip trip = Trip.from(member); - trip.add(new Point(1L, 1.1, 2.2, true, LocalDateTime.now())); - trip.add(new Point(2L, 3.3, 4.4, false, LocalDateTime.now())); - trip.add(new Point(3L, 5.5, 6.6, true, LocalDateTime.now())); + trip.add(new Point(1.1, 2.2, true, LocalDateTime.now())); + trip.add(new Point(3.3, 4.4, false, LocalDateTime.now())); + trip.add(new Point(5.5, 6.6, true, LocalDateTime.now())); // when List pointedLatitudes = trip.getPointedLatitudes(); @@ -253,9 +235,9 @@ class TripTest { // given Member member = new Member("통후추", "kakaoId", KAKAO); Trip trip = Trip.from(member); - trip.add(new Point(1L, 1.1, 2.2, true, LocalDateTime.now())); - trip.add(new Point(2L, 3.3, 4.4, false, LocalDateTime.now())); - trip.add(new Point(3L, 5.5, 6.6, true, LocalDateTime.now())); + trip.add(new Point(1.1, 2.2, true, LocalDateTime.now())); + trip.add(new Point(3.3, 4.4, false, LocalDateTime.now())); + trip.add(new Point(5.5, 6.6, true, LocalDateTime.now())); // when List pointedLongitudes = trip.getPointedLongitudes(); @@ -263,19 +245,4 @@ class TripTest { // then assertThat(pointedLongitudes).containsExactly(2.2, 6.6); } - - @Test - void 여행의_위치정보_중_삭제되지_않은_위치정보를_반환한다() { - // given - Member member = new Member("통후추", "kakaoId", KAKAO); - Trip trip = Trip.from(member); - Point point1 = new Point(1.1, 2.2, LocalDateTime.now()); - Point point2 = new Point(3.3, 4.4, LocalDateTime.now()); - trip.add(point1); - trip.add(point2); - point2.delete(); - - // expect - assertThat(trip.points()).containsExactly(point1); - } } diff --git a/backend/src/test/java/dev/tripdraw/dto/trip/PointCreateRequestTest.java b/backend/src/test/java/dev/tripdraw/trip/dto/PointCreateRequestTest.java similarity index 92% rename from backend/src/test/java/dev/tripdraw/dto/trip/PointCreateRequestTest.java rename to backend/src/test/java/dev/tripdraw/trip/dto/PointCreateRequestTest.java index a180db669..02525c70d 100644 --- a/backend/src/test/java/dev/tripdraw/dto/trip/PointCreateRequestTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/dto/PointCreateRequestTest.java @@ -1,8 +1,8 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; import static org.assertj.core.api.Assertions.assertThat; -import dev.tripdraw.domain.trip.Point; +import dev.tripdraw.trip.domain.Point; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; diff --git a/backend/src/test/java/dev/tripdraw/dto/trip/TripSearchResponseTest.java b/backend/src/test/java/dev/tripdraw/trip/dto/TripSearchResponseOfMemberTest.java similarity index 60% rename from backend/src/test/java/dev/tripdraw/dto/trip/TripSearchResponseTest.java rename to backend/src/test/java/dev/tripdraw/trip/dto/TripSearchResponseOfMemberTest.java index bf5575170..8f273eee4 100644 --- a/backend/src/test/java/dev/tripdraw/dto/trip/TripSearchResponseTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/dto/TripSearchResponseOfMemberTest.java @@ -1,19 +1,19 @@ -package dev.tripdraw.dto.trip; +package dev.tripdraw.trip.dto; -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.domain.trip.TripStatus.ONGOING; +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.trip.domain.TripStatus.ONGOING; import static org.assertj.core.api.Assertions.assertThat; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripName; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @SuppressWarnings("NonAsciiCharacters") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class TripSearchResponseTest { +class TripSearchResponseOfMemberTest { @Test void 여행_이미지와_경로_이미지가_null이면_빈값으로_변환해_생성한다() { @@ -23,11 +23,11 @@ class TripSearchResponseTest { Trip trip = new Trip(1L, tripName, member, ONGOING, null, null); // when - TripSearchResponse response = TripSearchResponse.from(trip); + TripSearchResponseOfMember response = TripSearchResponseOfMember.from(trip); // then assertThat(response).usingRecursiveComparison().isEqualTo( - TripSearchResponse.from(new Trip(1L, tripName, member, ONGOING, "", "")) + TripSearchResponseOfMember.from(new Trip(1L, tripName, member, ONGOING, "", "")) ); } } diff --git a/backend/src/test/java/dev/tripdraw/presentation/controller/TripControllerTest.java b/backend/src/test/java/dev/tripdraw/trip/presentation/TripControllerTest.java similarity index 55% rename from backend/src/test/java/dev/tripdraw/presentation/controller/TripControllerTest.java rename to backend/src/test/java/dev/tripdraw/trip/presentation/TripControllerTest.java index 294291a4d..b19d2c802 100644 --- a/backend/src/test/java/dev/tripdraw/presentation/controller/TripControllerTest.java +++ b/backend/src/test/java/dev/tripdraw/trip/presentation/TripControllerTest.java @@ -1,35 +1,46 @@ -package dev.tripdraw.presentation.controller; - -import static dev.tripdraw.domain.oauth.OauthType.KAKAO; -import static dev.tripdraw.domain.trip.TripStatus.FINISHED; +package dev.tripdraw.trip.presentation; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.test.fixture.TestFixture.pointCreateRequest; +import static dev.tripdraw.test.fixture.TestFixture.tripUpdateRequest; +import static dev.tripdraw.test.fixture.TestFixture.서울_2022_1_2_일; +import static dev.tripdraw.test.fixture.TestFixture.서울_2023_1_1_일; +import static dev.tripdraw.test.fixture.TestFixture.제주_2023_1_1_일; +import static dev.tripdraw.test.step.PostStep.createPostAtCurrentPoint; +import static dev.tripdraw.test.step.TripStep.addPointAndGetResponse; +import static dev.tripdraw.test.step.TripStep.createTripAndGetResponse; +import static dev.tripdraw.test.step.TripStep.searchTripAndGetResponse; +import static dev.tripdraw.test.step.TripStep.updateTrip; +import static dev.tripdraw.trip.domain.TripStatus.FINISHED; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.UNAUTHORIZED; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import dev.tripdraw.application.draw.RouteImageGenerator; -import dev.tripdraw.application.oauth.AuthTokenManager; -import dev.tripdraw.domain.member.Member; -import dev.tripdraw.domain.member.MemberRepository; -import dev.tripdraw.domain.trip.Trip; -import dev.tripdraw.domain.trip.TripRepository; -import dev.tripdraw.dto.trip.PointCreateRequest; -import dev.tripdraw.dto.trip.PointCreateResponse; -import dev.tripdraw.dto.trip.PointResponse; -import dev.tripdraw.dto.trip.TripCreateResponse; -import dev.tripdraw.dto.trip.TripResponse; -import dev.tripdraw.dto.trip.TripSearchResponse; -import dev.tripdraw.dto.trip.TripUpdateRequest; -import dev.tripdraw.dto.trip.TripsSearchResponse; +import dev.tripdraw.auth.application.JwtTokenProvider; +import dev.tripdraw.draw.application.RouteImageGenerator; +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.test.ControllerTest; +import dev.tripdraw.trip.dto.PointCreateRequest; +import dev.tripdraw.trip.dto.PointCreateResponse; +import dev.tripdraw.trip.dto.PointResponse; +import dev.tripdraw.trip.dto.TripCreateResponse; +import dev.tripdraw.trip.dto.TripResponse; +import dev.tripdraw.trip.dto.TripSearchResponse; +import dev.tripdraw.trip.dto.TripSearchResponseOfMember; +import dev.tripdraw.trip.dto.TripUpdateRequest; +import dev.tripdraw.trip.dto.TripsSearchResponse; +import dev.tripdraw.trip.dto.TripsSearchResponseOfMember; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -43,28 +54,26 @@ class TripControllerTest extends ControllerTest { private static final String WRONG_TOKEN = "wrong.long.token"; - @Autowired - private TripRepository tripRepository; - @Autowired private MemberRepository memberRepository; @Autowired - private AuthTokenManager authTokenManager; + private JwtTokenProvider jwtTokenProvider; @MockBean private RouteImageGenerator routeImageGenerator; - private Trip trip; private String huchuToken; + private String reoToken; @BeforeEach - void setUp() { + public void setUp() { super.setUp(); - Member member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); - trip = tripRepository.save(Trip.from(member)); - huchuToken = authTokenManager.generate(member.id()); + Member huchu = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + Member reo = memberRepository.save(new Member("리오", "kakaoId", KAKAO)); + huchuToken = jwtTokenProvider.generateAccessToken(huchu.id().toString()); + reoToken = jwtTokenProvider.generateAccessToken(reo.id().toString()); } @Test @@ -98,8 +107,9 @@ void setUp() { @Test void 여행에_위치_정보를_추가한다() { // given + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); PointCreateRequest request = new PointCreateRequest( - trip.id(), + tripResponse.tripId(), 1.1, 2.2, LocalDateTime.of(2023, 7, 18, 20, 24) @@ -126,8 +136,9 @@ void setUp() { @Test void 위치_정보_추가_시_인증에_실패하면_예외를_발생시킨다() { // given + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); PointCreateRequest request = new PointCreateRequest( - trip.id(), + tripResponse.tripId(), 1.1, 2.2, LocalDateTime.of(2023, 7, 18, 20, 24) @@ -145,10 +156,13 @@ void setUp() { @Test void 여행을_ID로_조회한다() { - // given & when + // given + Long tripId = createTripAndGetResponse(huchuToken).tripId(); + + // when ExtractableResponse response = RestAssured.given().log().all() .auth().preemptive().oauth2(huchuToken) - .when().get("/trips/{tripId}", trip.id()) + .when().get("/trips/{tripId}", tripId) .then().log().all() .extract(); @@ -167,28 +181,14 @@ void setUp() { @Test void 특정_위치정보를_삭제한다() { // given - PointCreateRequest pointCreateRequest = new PointCreateRequest( - trip.id(), - 1.1, - 2.2, - LocalDateTime.of(2023, 7, 18, 20, 24) - ); - - ExtractableResponse response = RestAssured.given().log().all() - .contentType(APPLICATION_JSON_VALUE) - .auth().preemptive().oauth2(huchuToken) - .body(pointCreateRequest) - .when().post("/points") - .then().log().all() - .extract(); - - PointResponse pointResponse = response.as(PointResponse.class); + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); + PointResponse pointResponse = addPointAndGetResponse(pointCreateRequest(tripResponse.tripId()), huchuToken); // expect RestAssured.given().log().all() .contentType(APPLICATION_JSON_VALUE) .auth().preemptive().oauth2(huchuToken) - .param("tripId", trip.id()) + .param("tripId", tripResponse.tripId()) .when().delete("/points/{pointId}", pointResponse.pointId()) .then().log().all() .statusCode(NO_CONTENT.value()); @@ -197,126 +197,45 @@ void setUp() { @Test void 특정_위치정보_삭제시_인증에_실패하면_예외를_발생시킨다() { // given - PointCreateRequest pointCreateRequest = new PointCreateRequest( - trip.id(), - 1.1, - 2.2, - LocalDateTime.of(2023, 7, 18, 20, 24) - ); - - ExtractableResponse response = RestAssured.given().log().all() - .contentType(APPLICATION_JSON_VALUE) - .auth().preemptive().oauth2(huchuToken) - .body(pointCreateRequest) - .when().post("/points") - .then().log().all() - .extract(); - - PointResponse pointResponse = response.as(PointResponse.class); + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); + PointResponse pointResponse = addPointAndGetResponse(pointCreateRequest(tripResponse.tripId()), huchuToken); // expect RestAssured.given().log().all() .contentType(APPLICATION_JSON_VALUE) .auth().preemptive().oauth2(WRONG_TOKEN) - .param("tripId", trip.id()) + .param("tripId", tripResponse.tripId()) .when().delete("/points/{pointId}", pointResponse.pointId()) .then().log().all() .statusCode(UNAUTHORIZED.value()); } @Test - void 특정_위치정보_삭제시_해당_여행에_존재하는_위치정보가_아니면_예외를_발생시킨다() { + void 특정_회원의_전체_여행을_조회한다() { // given + Long tripId = createTripAndGetResponse(huchuToken).tripId(); + updateTrip(tripUpdateRequest(), tripId, huchuToken); - PointCreateRequest pointCreateRequest = new PointCreateRequest( - trip.id(), - 1.1, - 2.2, - LocalDateTime.of(2023, 7, 18, 20, 24) - ); - - PointResponse pointResponse = RestAssured.given().log().all() - .contentType(APPLICATION_JSON_VALUE) - .auth().preemptive().oauth2(huchuToken) - .body(pointCreateRequest) - .when().post("/points") - .then().log().all() - .extract() - .as(PointResponse.class); - - TripResponse tripResponse = RestAssured.given().log().all() - .auth().preemptive().oauth2(huchuToken) - .when().post("/trips") - .then().log().all() - .extract() - .as(TripResponse.class); - - // expect - RestAssured.given().log().all() - .contentType(APPLICATION_JSON_VALUE) - .auth().preemptive().oauth2(huchuToken) - .param("tripId", tripResponse.tripId()) - .when().delete("/points/{pointId}", pointResponse.pointId()) - .then().log().all() - .statusCode(NOT_FOUND.value()); - } - - @Test - void 삭제된_위치정보를_삭제시_예외를_발생시킨다() { - // given - PointCreateRequest pointCreateRequest = new PointCreateRequest( - trip.id(), - 1.1, - 2.2, - LocalDateTime.of(2023, 7, 18, 20, 24) - ); - - ExtractableResponse response = RestAssured.given().log().all() - .contentType(APPLICATION_JSON_VALUE) - .auth().preemptive().oauth2(huchuToken) - .body(pointCreateRequest) - .when().post("/points") - .then().log().all() - .extract(); - - PointResponse pointResponse = response.as(PointResponse.class); - - RestAssured.given().log().all() - .auth().preemptive().oauth2(huchuToken) - .param("tripId", trip.id()) - .when().delete("/points/{pointId}", pointResponse.pointId()) - .then().log().all(); - - // expect - RestAssured.given().log().all() - .auth().preemptive().oauth2(huchuToken) - .param("tripId", trip.id()) - .when().delete("/points/{pointId}", pointResponse.pointId()) - .then().log().all() - .statusCode(CONFLICT.value()); - } - - @Test - void 전체_여행을_조회한다() { - // given + // when ExtractableResponse response = RestAssured.given().log().all() .auth().preemptive().oauth2(huchuToken) - .when().get("/trips") + .when().get("/trips/me") .then().log().all() .extract(); // then - TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + TripsSearchResponseOfMember tripsSearchResponseOfMember = response.as(TripsSearchResponseOfMember.class); assertSoftly(softly -> { softly.assertThat(response.statusCode()).isEqualTo(OK.value()); - softly.assertThat(tripsSearchResponse).usingRecursiveComparison().isEqualTo( - new TripsSearchResponse( - List.of(new TripSearchResponse( - trip.id(), - trip.nameValue(), - trip.imageUrl(), - trip.routeImageUrl()) + softly.assertThat(tripsSearchResponseOfMember).usingRecursiveComparison().isEqualTo( + new TripsSearchResponseOfMember( + List.of(new TripSearchResponseOfMember( + tripId, + "제주도 여행", + "", + "" + ) ) ) ); @@ -326,6 +245,7 @@ void setUp() { @Test void 여행의_이름과_상태를_수정한다() { // given + Long tripId = createTripAndGetResponse(huchuToken).tripId(); TripUpdateRequest tripUpdateRequest = new TripUpdateRequest("제주도 여행", FINISHED); // when @@ -333,35 +253,30 @@ void setUp() { .contentType(APPLICATION_JSON_VALUE) .auth().preemptive().oauth2(huchuToken) .body(tripUpdateRequest) - .when().patch("/trips/{tripId}", trip.id()) + .when().patch("/trips/{tripId}", tripId) .then().log().all() .extract(); // then - Trip updatedTrip = tripRepository.findById(trip.id()).get(); + TripResponse tripResponse = searchTripAndGetResponse(tripId, huchuToken); assertSoftly(softly -> { softly.assertThat(response.statusCode()).isEqualTo(NO_CONTENT.value()); - softly.assertThat(updatedTrip.nameValue()).isEqualTo("제주도 여행"); - softly.assertThat(updatedTrip.status()).isEqualTo(FINISHED); + softly.assertThat(tripResponse.name()).isEqualTo("제주도 여행"); + softly.assertThat(tripResponse.status()).isEqualTo(FINISHED); }); } @Test void 위치_정보를_조회한다() { // given - PointCreateRequest request = new PointCreateRequest( - trip.id(), - 1.1, - 2.2, - LocalDateTime.of(2023, 7, 18, 20, 24) - ); - Long pointId = createPointAndGetId(request).pointId(); + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); + Long pointId = addPointAndGetResponse(pointCreateRequest(tripResponse.tripId()), huchuToken).pointId(); // when ExtractableResponse response = RestAssured.given().log().all() .auth().preemptive().oauth2(huchuToken) - .param("tripId", trip.id()) + .param("tripId", tripResponse.tripId()) .when().get("/points/{pointId}", pointId) .then().log().all() .extract(); @@ -385,33 +300,73 @@ void setUp() { @Test void 여행을_삭제한다() { + // given + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); + Long tripId = tripResponse.tripId(); + // expect RestAssured.given().log().all() .auth().preemptive().oauth2(huchuToken) - .when().delete("/trips/{tripId}", trip.id()) + .when().delete("/trips/{tripId}", tripId) .then().log().all() .statusCode(NO_CONTENT.value()); } @Test void 여행을_삭제할_때_인증에_실패하면_예외가_발생한다() { + // given + TripCreateResponse tripResponse = createTripAndGetResponse(huchuToken); + Long tripId = tripResponse.tripId(); + // expect RestAssured.given().log().all() .auth().preemptive().oauth2(WRONG_TOKEN) - .when().delete("/trips/{tripId}", trip.id()) + .when().delete("/trips/{tripId}", tripId) .then().log().all() .statusCode(UNAUTHORIZED.value()); } - private PointCreateResponse createPointAndGetId(PointCreateRequest request) { + @Test + void 감상이_있는_모든_여행을_조건으로_조회할_수_있다() { + // given + Long 후추_서울_2022_1_2_일 = createTripAndGetResponse(huchuToken).tripId(); + createPostAtCurrentPoint(서울_2022_1_2_일(후추_서울_2022_1_2_일), huchuToken); + + Long 리오_제주_2023_1_1_일 = createTripAndGetResponse(reoToken).tripId(); + createPostAtCurrentPoint(제주_2023_1_1_일(리오_제주_2023_1_1_일), reoToken); + + Long 리오_서울_2023_1_1_일 = createTripAndGetResponse(reoToken).tripId(); + createPostAtCurrentPoint(서울_2023_1_1_일(리오_서울_2023_1_1_일), reoToken); + + Map params = Map.of( + "years", Set.of(2023, 2021), + "daysOfWeek", Set.of(1), + "address", "seoul", + "limit", 10 + ); + + // when ExtractableResponse response = RestAssured.given().log().all() - .contentType(APPLICATION_JSON_VALUE) .auth().preemptive().oauth2(huchuToken) - .body(request) - .when().post("/points") + .params(params) + .when().get("/trips") .then().log().all() .extract(); - return response.as(PointCreateResponse.class); + // then + TripsSearchResponse tripsSearchResponse = response.as(TripsSearchResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(OK.value()); + softly.assertThat(searchedTripIds(tripsSearchResponse)).containsExactly( + 리오_서울_2023_1_1_일 + ); + }); + } + + private List searchedTripIds(TripsSearchResponse tripsSearchResponse) { + return tripsSearchResponse.trips().stream() + .map(TripSearchResponse::tripId) + .toList(); } } diff --git a/backend/src/test/java/dev/tripdraw/trip/query/TripCustomRepositoryImplTest.java b/backend/src/test/java/dev/tripdraw/trip/query/TripCustomRepositoryImplTest.java new file mode 100644 index 000000000..cc69dde71 --- /dev/null +++ b/backend/src/test/java/dev/tripdraw/trip/query/TripCustomRepositoryImplTest.java @@ -0,0 +1,474 @@ +package dev.tripdraw.trip.query; + +import dev.tripdraw.member.domain.Member; +import dev.tripdraw.member.domain.MemberRepository; +import dev.tripdraw.post.domain.Post; +import dev.tripdraw.post.domain.PostRepository; +import dev.tripdraw.trip.domain.Point; +import dev.tripdraw.trip.domain.Trip; +import dev.tripdraw.trip.domain.TripRepository; +import dev.tripdraw.trip.dto.TripSearchConditions; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; + +import static dev.tripdraw.common.auth.OauthType.KAKAO; +import static dev.tripdraw.test.fixture.TestFixture.위치정보; +import static dev.tripdraw.test.fixture.TripSearchConditionsFixture.*; +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@Transactional +@SpringBootTest +class TripCustomRepositoryImplTest { + + @Autowired + private TripCustomRepository tripCustomRepository; + + @Autowired + private TripRepository tripRepository; + + @Autowired + private PostRepository postRepository; + + @Autowired + private MemberRepository memberRepository; + + private Member member; + + @BeforeEach + void setUp() { + member = memberRepository.save(new Member("통후추", "kakaoId", KAKAO)); + } + + @Nested + class 조건에_따라_여행을_조회할_때 { + + @Nested + class 개수_제한이 { + + @ParameterizedTest + @CsvSource({"0, 1", "1, 2", "2, 3"}) + void 여행의_개수보다_적으면_개수_제한보다_하나_더_반환한다(int limit, int expectedSize) { + // given + jeju_2023_1_1_Sun(); + jeju_2023_1_1_Sun(); + jeju_2023_1_1_Sun(); + + TripPaging tripPaging = new TripPaging(null, limit); + + // when + List trips = tripCustomRepository.findAllByConditions(emptyTripSearchConditions(), tripPaging); + + // then + assertThat(trips).hasSize(expectedSize); + } + + @ParameterizedTest + @CsvSource({"1, 1", "2, 1"}) + void 여행의_개수보다_많거나_같으면_모든_여행을_반환한다(int limit, int expectedSize) { + // given + jeju_2023_1_1_Sun(); + + TripPaging tripPaging = new TripPaging(null, limit); + + // when + List trips = tripCustomRepository.findAllByConditions(emptyTripSearchConditions(), tripPaging); + + // then + assertThat(trips).hasSize(expectedSize); + } + } + + @Nested + class 마지막으로_조회한_Id를 { + + @Test + void 입력하지_않으면_가장_최신_여행부터_내림차순으로_반환한다() { + // given + Trip jeju_2023_1_1_sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + Trip jeju2023_2_1_Wed = jeju_2023_2_1_Wed(); + + Long lastViewedId = null; + + // when + List trips = tripCustomRepository.findAllByConditions( + emptyTripSearchConditions(), + new TripPaging(lastViewedId, 10) + ); + + // then + assertThat(trips).containsExactly( + jeju2023_2_1_Wed, + seoul2023_1_1_Sun, + jeju_2023_1_1_sun + ); + } + + @Test + void 입력하면_해당_Id_이하의_최신_여행을_내림차순으로_반환한다() { + // given + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + Trip jeju2023_2_1_Wed = jeju_2023_2_1_Wed(); + + Long lastViewedId = jeju2023_2_1_Wed.id(); + + // when + List trips = tripCustomRepository.findAllByConditions( + emptyTripSearchConditions(), + new TripPaging(lastViewedId, 10) + ); + + // then + assertThat(trips).containsExactly(seoul2023_1_1_Sun, jeju2023_1_1_Sun); + } + } + + @Nested + class 조건으로_연도를 { + + @Test + void 한_개_설정하면_해당_연도의_여행을_반환한다() { + // given + seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + Trip jeju2023_2_1_Wed = jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = yearsTripSearchConditions(Set.of(2023)); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(jeju2023_2_1_Wed, seoul2023_1_1_Sun, jeju2023_1_1_Sun); + } + + @Test + void 여러_개_설정하면_해당_연도들의_여행을_반환한다() { + // given + Trip yangyang2021_3_2_Tue = yangyang_2021_3_2_Tue(); + seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + Trip jeju2023_2_1_Wed = jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = yearsTripSearchConditions(Set.of(2023, 2021)); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly( + jeju2023_2_1_Wed, + seoul2023_1_1_Sun, + jeju2023_1_1_Sun, + yangyang2021_3_2_Tue + ); + } + } + + @Nested + class 조건으로_월을 { + + @Test + void 한_개_설정하면_해당_월의_여행을_반환한다() { + // given + Trip seoul2022_1_2_Sun = seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = monthsTripSearchConditions(Set.of(1)); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(seoul2023_1_1_Sun, jeju2023_1_1_Sun, seoul2022_1_2_Sun); + } + + @Test + void 여러_개_설정하면_해당_월들의_여행을_반환한다() { + // given + Trip yangyang2021_3_2_Tue = yangyang_2021_3_2_Tue(); + Trip seoul2022_1_2_Sun = seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = monthsTripSearchConditions(Set.of(1, 3)); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly( + seoul2023_1_1_Sun, + jeju2023_1_1_Sun, + seoul2022_1_2_Sun, + yangyang2021_3_2_Tue + ); + } + } + + @Nested + class 조건으로_요일을 { + + @Test + void 한_개_설정하면_해당_요일의_여행을_반환한다() { + // given + Trip seoul2022_1_2_Sun = seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = daysOfWeekTripSearchConditions(Set.of(1)); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(seoul2023_1_1_Sun, jeju2023_1_1_Sun, seoul2022_1_2_Sun); + } + + @Test + void 여러_개_설정하면_해당_요일들의_여행을_반환한다() { + // given + Trip yangyang2021_3_2_Tue = yangyang_2021_3_2_Tue(); + Trip seoul2022_1_2_Sun = seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = daysOfWeekTripSearchConditions(Set.of(1, 3)); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly( + seoul2023_1_1_Sun, + jeju2023_1_1_Sun, + seoul2022_1_2_Sun, + yangyang2021_3_2_Tue + ); + } + } + + @Nested + class 조건으로_주소를 { + + @Test + void 시도_시군구_읍면동_형식으로_입력하면_해당하는_여행을_반환한다() { + // given + Trip seoul_songpa_Bangi = seoul_2022_1_2_Sun(); + Trip jejuIsland_jeju_aewol = jeju_2023_1_1_Sun(); + Trip seoul_songpa_sincheon = seoul_2023_1_1_Sun(); + + TripSearchConditions tripSearchConditions = addressTripSearchConditions("서울특별시 송파구 신천동"); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(seoul_songpa_sincheon); + } + + @Test + void 시도_시군구_형식으로_입력하면_해당하는_여행을_반환한다() { + // given + Trip seoul_songpa_Bangi = seoul_2022_1_2_Sun(); + Trip jejuIsland_jeju_aewol = jeju_2023_1_1_Sun(); + Trip seoul_songpa_sincheon = seoul_2023_1_1_Sun(); + + TripSearchConditions tripSearchConditions = addressTripSearchConditions("서울특별시 송파구"); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(seoul_songpa_sincheon, seoul_songpa_Bangi); + } + + @Test + void 시도_형식으로_입력하면_해당하는_여행을_반환한다() { + // given + Trip seoul_songpa_Bangi = seoul_2022_1_2_Sun(); + Trip jejuIsland_jeju_aewol = jeju_2023_1_1_Sun(); + Trip seoul_songpa_sincheon = seoul_2023_1_1_Sun(); + + TripSearchConditions tripSearchConditions = addressTripSearchConditions("서울특별시"); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(seoul_songpa_sincheon, seoul_songpa_Bangi); + } + } + + @Nested + class 조건을 { + + @Test + void 여러_개_설정하면_해당하는_여행을_반환한다() { + // given + yangyang_2021_3_2_Tue(); + seoul_2022_1_2_Sun(); + jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = TripSearchConditions.builder() + .years(Set.of(2023)) + .months(Set.of(1)) + .address("서울특별시") + .build(); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly(seoul2023_1_1_Sun); + } + + @Test + void 설정하지_않으면_모든_여행을_반환한다() { + // given + Trip yangyang2021_3_2_Tue = yangyang_2021_3_2_Tue(); + Trip seoul2022_1_2_Sun = seoul_2022_1_2_Sun(); + Trip jeju2023_1_1_Sun = jeju_2023_1_1_Sun(); + Trip seoul2023_1_1_Sun = seoul_2023_1_1_Sun(); + Trip jeju_2023_2_1_Wed = jeju_2023_2_1_Wed(); + + TripSearchConditions tripSearchConditions = emptyTripSearchConditions(); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).containsExactly( + jeju_2023_2_1_Wed, + seoul2023_1_1_Sun, + jeju2023_1_1_Sun, + seoul2022_1_2_Sun, + yangyang2021_3_2_Tue + ); + } + } + + @Test + void 감상이_없는_여행은_조회되지_않는다() { + // given + emptyPostTrip(); + + TripSearchConditions tripSearchConditions = emptyTripSearchConditions(); + + // when + List trips = tripCustomRepository.findAllByConditions( + tripSearchConditions, + new TripPaging(null, 10) + ); + + // then + assertThat(trips).isEmpty(); + } + + private Trip jeju_2023_2_1_Wed() { + Trip trip = Trip.from(member); + Point point = 위치정보(2023, 2, 1, 1, 1); + trip.add(point); + tripRepository.save(trip); + postRepository.save(new Post("", point, "제주특별자치도 제주시 애월읍", "", member, trip.id())); + return trip; + } + + private Trip seoul_2023_1_1_Sun() { + Trip trip = Trip.from(member); + Point point = 위치정보(2023, 1, 1, 10, 1); + trip.add(point); + tripRepository.save(trip); + postRepository.save(new Post("", point, "서울특별시 송파구 신천동", "", member, trip.id())); + return trip; + } + + private Trip jeju_2023_1_1_Sun() { + Trip trip = Trip.from(member); + Point point = 위치정보(2023, 1, 1, 1, 1); + trip.add(point); + tripRepository.save(trip); + postRepository.save(new Post("", point, "제주특별자치도 제주시 애월읍", "", member, trip.id())); + return trip; + } + + private Trip seoul_2022_1_2_Sun() { + Trip trip = Trip.from(member); + Point point = 위치정보(2022, 1, 2, 1, 1); + trip.add(point); + tripRepository.save(trip); + postRepository.save(new Post("", point, "서울특별시 송파구 방이동", "", member, trip.id())); + return trip; + } + + private Trip yangyang_2021_3_2_Tue() { + Trip trip = Trip.from(member); + Point point = 위치정보(2021, 3, 2, 1, 1); + trip.add(point); + tripRepository.save(trip); + postRepository.save(new Post("", point, "강원도 양양군", "", member, trip.id())); + return trip; + } + + private Trip emptyPostTrip() { + Trip trip = Trip.from(member); + Point point = 위치정보(2021, 3, 2, 1, 1); + trip.add(point); + tripRepository.save(trip); + return trip; + } + } + +} diff --git a/sub-2023-trip-draw b/sub-2023-trip-draw index 792706acf..c98599a51 160000 --- a/sub-2023-trip-draw +++ b/sub-2023-trip-draw @@ -1 +1 @@ -Subproject commit 792706acf760803367ee7dc21d1f2b4b5bfd4c2e +Subproject commit c98599a5179689f84b5b494365ff464ddacc0069