diff --git a/.github/workflows/backend-dev-cd.yml b/.github/workflows/backend-dev-cd.yml index 808b9f882..2dbbd946d 100644 --- a/.github/workflows/backend-dev-cd.yml +++ b/.github/workflows/backend-dev-cd.yml @@ -66,8 +66,18 @@ jobs: with: name: bang-ggood-be-develop-jar + - name: Create unhealth_flag file + run: echo "unhealth" | sudo tee /etc/nginx/sites-available/unhealth_flag.txt > /dev/null + + + - name: Sleep for 30 seconds + run: sleep 30 + - name: Turn off the server 8080 if runs run: sudo fuser -k -n tcp 8080 || true - + - name: Start server run: sudo nohup java -jar -Dspring.profiles.active=dev -Duser.timezone=Asia/Seoul ./backend/bang-ggood/build/libs/*SNAPSHOT.jar > /home/ubuntu/actions-runner/server.log 2>&1 & + + - name: Delete unhealth_flag file + run: sudo rm /etc/nginx/sites-available/unhealth_flag.txt diff --git a/backend/bang-ggood/.DS_Store b/backend/bang-ggood/.DS_Store new file mode 100644 index 000000000..2f804dcc3 Binary files /dev/null and b/backend/bang-ggood/.DS_Store differ diff --git a/backend/bang-ggood/build.gradle b/backend/bang-ggood/build.gradle index 483a11f04..9d9343fbd 100644 --- a/backend/bang-ggood/build.gradle +++ b/backend/bang-ggood/build.gradle @@ -23,11 +23,15 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'org.projectlombok:lombok' + implementation 'com.mysql:mysql-connector-j' + implementation 'com.opencsv:opencsv:5.9' + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' implementation 'io.jsonwebtoken:jjwt-gson:0.11.2' - implementation 'com.mysql:mysql-connector-j' - implementation 'com.opencsv:opencsv:5.9' + + annotationProcessor 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/backend/bang-ggood/src/.DS_Store b/backend/bang-ggood/src/.DS_Store new file mode 100644 index 000000000..f6de40efe Binary files /dev/null and b/backend/bang-ggood/src/.DS_Store differ diff --git a/backend/bang-ggood/src/main/.DS_Store b/backend/bang-ggood/src/main/.DS_Store new file mode 100644 index 000000000..a6d2d58b9 Binary files /dev/null and b/backend/bang-ggood/src/main/.DS_Store differ diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/article/controller/ArticleController.java b/backend/bang-ggood/src/main/java/com/bang_ggood/article/controller/ArticleController.java index 49ecf92f8..ba05787a9 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/article/controller/ArticleController.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/article/controller/ArticleController.java @@ -29,7 +29,7 @@ public ArticleController(ArticleService articleService) { public ResponseEntity createArticle(@AuthRequiredPrincipal User user, @Valid @RequestBody ArticleCreateRequest request) { Long id = articleService.createArticle(request); - return ResponseEntity.created(URI.create("articles/" + id)).build(); + return ResponseEntity.created(URI.create("/article/" + id)).build(); } @GetMapping("/articles/{id}") diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/article/domain/Article.java b/backend/bang-ggood/src/main/java/com/bang_ggood/article/domain/Article.java index 76da77fc1..988238da9 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/article/domain/Article.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/article/domain/Article.java @@ -5,8 +5,14 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class Article extends BaseEntity { @@ -32,33 +38,6 @@ public Article(String title, String content, String keyword, String summary, Str this.thumbnail = thumbnail; } - protected Article() { - } - - public Long getId() { - return id; - } - - public String getTitle() { - return title; - } - - public String getContent() { - return content; - } - - public String getKeyword() { - return keyword; - } - - public String getSummary() { - return summary; - } - - public String getThumbnail() { - return thumbnail; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/article/repository/ArticleRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/article/repository/ArticleRepository.java index 53c8adce7..eee97d7a1 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/article/repository/ArticleRepository.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/article/repository/ArticleRepository.java @@ -23,7 +23,7 @@ default Article getById(Long id) { @Query("SELECT a FROM Article a " + "WHERE a.deleted = false " + - "ORDER BY a.createdAt DESC ") + "ORDER BY a.createdAt DESC, a.id DESC") List
findLatestArticles(); @Modifying(flushAutomatically = true, clearAutomatically = true) diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/article/service/ArticleService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/article/service/ArticleService.java index 2cbfd4784..e76eb776a 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/article/service/ArticleService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/article/service/ArticleService.java @@ -2,23 +2,21 @@ import com.bang_ggood.article.domain.Article; import com.bang_ggood.article.dto.request.ArticleCreateRequest; -import com.bang_ggood.article.dto.response.ArticlesResponse; import com.bang_ggood.article.dto.response.ArticleResponse; +import com.bang_ggood.article.dto.response.ArticlesResponse; import com.bang_ggood.article.dto.response.ArticlesResponses; import com.bang_ggood.article.repository.ArticleRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +@RequiredArgsConstructor @Service public class ArticleService { private final ArticleRepository articleRepository; - public ArticleService(ArticleRepository articleRepository) { - this.articleRepository = articleRepository; - } - @Transactional public long createArticle(ArticleCreateRequest request) { Article article = request.toEntity(); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/AuthRequiredPrincipalArgumentResolver.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/AuthRequiredPrincipalArgumentResolver.java index 0e34a9d55..7761d0cc0 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/AuthRequiredPrincipalArgumentResolver.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/AuthRequiredPrincipalArgumentResolver.java @@ -1,9 +1,7 @@ package com.bang_ggood.auth.config; -import com.bang_ggood.auth.controller.CookieResolver; +import com.bang_ggood.auth.controller.cookie.CookieResolver; import com.bang_ggood.auth.service.AuthService; -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.user.domain.User; import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; @@ -35,12 +33,8 @@ public User resolveArgument(MethodParameter parameter, ModelAndViewContainer mav NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - // TODO 리팩토링 - if (request.getCookies() == null || cookieResolver.isTokenNotExist(request.getCookies())) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_EMPTY); - } - - String token = cookieResolver.extractAccessToken(request.getCookies()); + cookieResolver.checkLoginRequired(request); + String token = cookieResolver.extractAccessToken(request); return authService.getAuthUser(token); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/UserPrincipalArgumentResolver.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/UserPrincipalArgumentResolver.java index 1573d3bc7..c628c799f 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/UserPrincipalArgumentResolver.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/config/UserPrincipalArgumentResolver.java @@ -1,6 +1,6 @@ package com.bang_ggood.auth.config; -import com.bang_ggood.auth.controller.CookieResolver; +import com.bang_ggood.auth.controller.cookie.CookieResolver; import com.bang_ggood.auth.service.AuthService; import com.bang_ggood.user.domain.User; import jakarta.servlet.http.HttpServletRequest; @@ -33,11 +33,11 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - if (request.getCookies() == null || cookieResolver.isTokenNotExist(request.getCookies())) { + if (cookieResolver.isTokenEmpty(request)) { return authService.assignGuestUser(); } - String token = cookieResolver.extractAccessToken(request.getCookies()); + String token = cookieResolver.extractAccessToken(request); return authService.getAuthUser(token); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/AuthController.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/AuthController.java index 62995cef3..0ccfaa2f6 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/AuthController.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/AuthController.java @@ -1,20 +1,29 @@ package com.bang_ggood.auth.controller; import com.bang_ggood.auth.config.AuthRequiredPrincipal; +import com.bang_ggood.auth.controller.cookie.CookieProvider; +import com.bang_ggood.auth.controller.cookie.CookieResolver; +import com.bang_ggood.auth.dto.request.LocalLoginRequestV1; import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.dto.request.RegisterRequestV1; import com.bang_ggood.auth.dto.response.AuthTokenResponse; +import com.bang_ggood.auth.dto.response.TokenExistResponse; import com.bang_ggood.auth.service.AuthService; import com.bang_ggood.user.domain.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; +import java.net.URI; +@RequiredArgsConstructor @RestController public class AuthController { @@ -22,15 +31,21 @@ public class AuthController { private final CookieProvider cookieProvider; private final CookieResolver cookieResolver; - public AuthController(AuthService authService, CookieProvider cookieProvider, CookieResolver cookieResolver) { - this.authService = authService; - this.cookieProvider = cookieProvider; - this.cookieResolver = cookieResolver; + @PostMapping("/v1/local-auth/register") + public ResponseEntity register(@Valid @RequestBody RegisterRequestV1 request) { + Long userId = authService.register(request); + return ResponseEntity.created(URI.create("/v1/local-auth/register/" + userId)).build(); + } + + @DeleteMapping("/v1/withdraw") + public ResponseEntity withdraw(@AuthRequiredPrincipal User user) { + authService.withdraw(user); + return ResponseEntity.noContent().build(); } @PostMapping("/oauth/login") - public ResponseEntity login(@Valid @RequestBody OauthLoginRequest request) { - AuthTokenResponse response = authService.login(request); + public ResponseEntity oauthLogin(@Valid @RequestBody OauthLoginRequest request) { + AuthTokenResponse response = authService.oauthLogin(request); ResponseCookie accessTokenCookie = cookieProvider.createAccessTokenCookie(response.accessToken()); ResponseCookie refreshTokenCookie = cookieProvider.createRefreshTokenCookie(response.refreshToken()); @@ -41,14 +56,29 @@ public ResponseEntity login(@Valid @RequestBody OauthLoginRequest request) .build(); } - @PostMapping("/oauth/logout") + @PostMapping("/v1/local-auth/login") + public ResponseEntity localLogin(@Valid @RequestBody LocalLoginRequestV1 request) { + AuthTokenResponse response = authService.localLogin(request); + + ResponseCookie accessTokenCookie = cookieProvider.createAccessTokenCookie(response.accessToken()); + ResponseCookie refreshTokenCookie = cookieProvider.createRefreshTokenCookie(response.refreshToken()); + + return ResponseEntity.ok() + .header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()) + .header(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString()) + .build(); + } + + @PostMapping("/v1/logout") public ResponseEntity logout(@AuthRequiredPrincipal User user, HttpServletRequest httpServletRequest) { - String accessToken = cookieResolver.extractAccessToken(httpServletRequest.getCookies()); - String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest.getCookies()); + String accessToken = cookieResolver.extractAccessToken(httpServletRequest); + String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest); + authService.logout(accessToken, refreshToken, user); - ResponseCookie deletedAccessTokenCookie = cookieProvider.deleteAccessTokenCookie(accessToken); - ResponseCookie deletedRefreshTokenCookie = cookieProvider.deleteRefreshTokenCookie(refreshToken); + + ResponseCookie deletedAccessTokenCookie = cookieProvider.deleteAccessTokenCookie(); + ResponseCookie deletedRefreshTokenCookie = cookieProvider.deleteRefreshTokenCookie(); return ResponseEntity.noContent() .header(HttpHeaders.SET_COOKIE, deletedAccessTokenCookie.toString()) @@ -57,13 +87,35 @@ public ResponseEntity logout(@AuthRequiredPrincipal User user, } @PostMapping("/accessToken/reissue") - public ResponseEntity reIssueAccessToken(HttpServletRequest httpServletRequest) { - String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest.getCookies()); - String accessToken = authService.reIssueAccessToken(refreshToken); + public ResponseEntity reissueAccessToken(HttpServletRequest httpServletRequest) { + cookieResolver.checkLoginRequired(httpServletRequest); + + String refreshToken = cookieResolver.extractRefreshToken(httpServletRequest); + String accessToken = authService.reissueAccessToken(refreshToken); ResponseCookie accessTokenCookie = cookieProvider.createAccessTokenCookie(accessToken); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()) .build(); } + + @GetMapping("/token-exist") + public ResponseEntity check(HttpServletRequest httpServletRequest) { + boolean isAccessTokenExist = !cookieResolver.isAccessTokenEmpty(httpServletRequest); + boolean isRefreshTokenExist = !cookieResolver.isRefreshTokenEmpty(httpServletRequest); + + TokenExistResponse tokenExistResponse = TokenExistResponse.from(isAccessTokenExist, isRefreshTokenExist); + return ResponseEntity.ok(tokenExistResponse); + } + + @DeleteMapping("/token") + public ResponseEntity deleteToken() { + ResponseCookie deletedAccessTokenCookie = cookieProvider.deleteAccessTokenCookie(); + ResponseCookie deletedRefreshTokenCookie = cookieProvider.deleteRefreshTokenCookie(); + + return ResponseEntity.noContent() + .header(HttpHeaders.SET_COOKIE, deletedAccessTokenCookie.toString()) + .header(HttpHeaders.SET_COOKIE, deletedRefreshTokenCookie.toString()) + .build(); + } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/CookieResolver.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/CookieResolver.java deleted file mode 100644 index 3586fa08f..000000000 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/CookieResolver.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.bang_ggood.auth.controller; - -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; -import jakarta.servlet.http.Cookie; -import org.springframework.stereotype.Component; -import java.util.Arrays; - -@Component -public class CookieResolver { - - public String extractAccessToken(Cookie[] cookies) { - return extractToken(cookies, CookieProvider.ACCESS_TOKEN_COOKIE_NAME); - } - - public String extractRefreshToken(Cookie[] cookies) { - return extractToken(cookies, CookieProvider.REFRESH_TOKEN_COOKIE_NAME); - } - - private String extractToken(Cookie[] cookies, String cookieName) { - if (cookies == null || cookies.length == 0) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_COOKIE_EMPTY); - } - - return Arrays.stream(cookies) - .filter(cookie -> cookie.getName().equals(cookieName)) - .findAny() - .map(Cookie::getValue) - .orElseThrow(() -> new BangggoodException(ExceptionCode.AUTHENTICATION_REQUIRED_TOKEN_EMPTY)); - } - - public boolean isTokenNotExist(Cookie[] cookies) { - return (!isAccessTokenExist(cookies) && !isRefreshTokenExist(cookies)); - } - - private boolean isAccessTokenExist(Cookie[] cookies) { - return isTokenExist(cookies, CookieProvider.ACCESS_TOKEN_COOKIE_NAME); - } - - private boolean isRefreshTokenExist(Cookie[] cookies) { - return isTokenExist(cookies, CookieProvider.REFRESH_TOKEN_COOKIE_NAME); - } - - private boolean isTokenExist(Cookie[] cookies, String cookieName) { - return Arrays.stream(cookies) - .anyMatch(cookie -> cookie.getName().equals(cookieName)); - } -} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/CookieProvider.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/cookie/CookieProvider.java similarity index 83% rename from backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/CookieProvider.java rename to backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/cookie/CookieProvider.java index 6e8561592..623a66394 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/CookieProvider.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/cookie/CookieProvider.java @@ -1,6 +1,6 @@ -package com.bang_ggood.auth.controller; +package com.bang_ggood.auth.controller.cookie; -import com.bang_ggood.auth.service.JwtTokenProperties; +import com.bang_ggood.auth.service.jwt.JwtTokenProperties; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; @@ -9,8 +9,9 @@ @Component public class CookieProvider { - public static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken"; - public static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; + protected static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken"; + protected static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; + private final JwtTokenProperties jwtTokenProperties; private final String domain; @@ -46,11 +47,11 @@ private ResponseCookie createCookie(String tokenName, String token, long expired .build(); } - public ResponseCookie deleteAccessTokenCookie(String token) { + public ResponseCookie deleteAccessTokenCookie() { return deleteCookie(ACCESS_TOKEN_COOKIE_NAME); } - public ResponseCookie deleteRefreshTokenCookie(String token) { + public ResponseCookie deleteRefreshTokenCookie() { return deleteCookie(REFRESH_TOKEN_COOKIE_NAME); } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/cookie/CookieResolver.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/cookie/CookieResolver.java new file mode 100644 index 000000000..5d69df020 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/controller/cookie/CookieResolver.java @@ -0,0 +1,65 @@ +package com.bang_ggood.auth.controller.cookie; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import java.util.Arrays; +import java.util.Optional; + +@Component +public class CookieResolver { + + public void checkLoginRequired(HttpServletRequest request) { + if (isTokenEmpty(request)) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_EMPTY); + } + + if (isRefreshTokenEmpty(request)) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_REFRESH_TOKEN_EMPTY); + } + } + + public boolean isAccessTokenEmpty(HttpServletRequest request) { + return isTokenEmpty(request, CookieProvider.ACCESS_TOKEN_COOKIE_NAME); + } + + public boolean isRefreshTokenEmpty(HttpServletRequest request) { + return isTokenEmpty(request, CookieProvider.REFRESH_TOKEN_COOKIE_NAME); + } + + private boolean isTokenEmpty(HttpServletRequest request, String cookieName) { + if (request.getCookies() == null) { + return true; + } + + return Arrays.stream(request.getCookies()) + .noneMatch(cookie -> cookie.getName().equals(cookieName)); + } + + public String extractAccessToken(HttpServletRequest request) { + return extractToken(request, CookieProvider.ACCESS_TOKEN_COOKIE_NAME) + .orElseThrow(() -> new BangggoodException(ExceptionCode.AUTHENTICATION_ACCESS_TOKEN_EMPTY)); + } + + public String extractRefreshToken(HttpServletRequest request) { + return extractToken(request, CookieProvider.REFRESH_TOKEN_COOKIE_NAME) + .orElseThrow(() -> new BangggoodException(ExceptionCode.AUTHENTICATION_REFRESH_TOKEN_EMPTY)); + } + + private Optional extractToken(HttpServletRequest request, String cookieName) { + if (isTokenEmpty(request)) { + return Optional.empty(); + } + + return Arrays.stream(request.getCookies()) + .filter(cookie -> cookie.getName().equals(cookieName)) + .findAny() + .map(Cookie::getValue); + } + + public boolean isTokenEmpty(HttpServletRequest request) { + return isAccessTokenEmpty(request) && isRefreshTokenEmpty(request); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/LocalLoginRequestV1.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/LocalLoginRequestV1.java new file mode 100644 index 000000000..82d558aa6 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/LocalLoginRequestV1.java @@ -0,0 +1,7 @@ +package com.bang_ggood.auth.dto.request; + +import jakarta.validation.constraints.NotBlank; + +public record LocalLoginRequestV1(@NotBlank(message = "이메일이 존재하지 않습니다.") String email, + @NotBlank(message = "비밀번호가 존재하지 않습니다.") String password) { +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/OauthLoginRequest.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/OauthLoginRequest.java index 6123cb9d5..d7d0107de 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/OauthLoginRequest.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/OauthLoginRequest.java @@ -2,5 +2,6 @@ import jakarta.validation.constraints.NotBlank; -public record OauthLoginRequest(@NotBlank(message = "인가 코드가 존재하지 않습니다.") String code) { +public record OauthLoginRequest(@NotBlank(message = "인가 코드가 존재하지 않습니다.") String code, + @NotBlank(message = "Redirect Uri가 존재하지 않습니다.") String redirectUri) { } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/RegisterRequestV1.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/RegisterRequestV1.java new file mode 100644 index 000000000..c82381d0d --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/request/RegisterRequestV1.java @@ -0,0 +1,15 @@ +package com.bang_ggood.auth.dto.request; + +import com.bang_ggood.user.domain.LoginType; +import com.bang_ggood.user.domain.User; +import com.bang_ggood.user.domain.UserType; +import jakarta.validation.constraints.NotBlank; + +public record RegisterRequestV1(@NotBlank(message = "이름이 존재하지 않습니다.") String name, + @NotBlank(message = "이메일이 존재하지 않습니다.") String email, + @NotBlank(message = "비밀번호가 존재하지 않습니다.") String password) { + + public User toUserEntity() { + return new User(name(), email(), password(), UserType.USER, LoginType.LOCAL); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/OauthInfoApiResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/OauthInfoApiResponse.java index 1633f11bd..a001283c6 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/OauthInfoApiResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/OauthInfoApiResponse.java @@ -1,5 +1,6 @@ package com.bang_ggood.auth.dto.response; +import com.bang_ggood.user.domain.LoginType; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.domain.UserType; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -8,6 +9,6 @@ public record OauthInfoApiResponse(String id, String connected_at, KakaoAccountResponse kakao_account) { public User toUserEntity() { - return new User(kakao_account.profile().nickname(), kakao_account.email(), UserType.USER); + return new User(kakao_account.profile().nickname(), kakao_account.email(), UserType.USER, LoginType.KAKAO); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/TokenExistResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/TokenExistResponse.java new file mode 100644 index 000000000..98cc46d6a --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/dto/response/TokenExistResponse.java @@ -0,0 +1,8 @@ +package com.bang_ggood.auth.dto.response; + +public record TokenExistResponse(boolean isAccessTokenExist, boolean isRefreshTokenExist) { + + public static TokenExistResponse from(boolean isAccessTokenExist, boolean isRefreshTokenExist) { + return new TokenExistResponse(isAccessTokenExist, isRefreshTokenExist); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java index 4be0b075e..cb47bb416 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java @@ -1,20 +1,30 @@ package com.bang_ggood.auth.service; +import com.bang_ggood.auth.dto.request.LocalLoginRequestV1; import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.dto.request.RegisterRequestV1; import com.bang_ggood.auth.dto.response.AuthTokenResponse; import com.bang_ggood.auth.dto.response.OauthInfoApiResponse; +import com.bang_ggood.auth.service.jwt.JwtTokenProvider; +import com.bang_ggood.auth.service.jwt.JwtTokenResolver; +import com.bang_ggood.auth.service.oauth.OauthClient; import com.bang_ggood.global.DefaultChecklistService; import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; +import com.bang_ggood.user.domain.Email; +import com.bang_ggood.user.domain.LoginType; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.domain.UserType; import com.bang_ggood.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +@RequiredArgsConstructor @Service public class AuthService { @@ -23,22 +33,30 @@ public class AuthService { private final OauthClient oauthClient; private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenResolver jwtTokenResolver; private final DefaultChecklistService defaultChecklistService; - private final UserRepository userRepository; // TODO 리팩토링 - - public AuthService(OauthClient oauthClient, JwtTokenProvider jwtTokenProvider, - DefaultChecklistService defaultChecklistService, UserRepository userRepository) { - this.oauthClient = oauthClient; - this.jwtTokenProvider = jwtTokenProvider; - this.defaultChecklistService = defaultChecklistService; - this.userRepository = userRepository; + private final UserRepository userRepository; + + @Transactional + public Long register(RegisterRequestV1 request) { + try { + return userRepository.save(request.toUserEntity()).getId(); + } catch (DataIntegrityViolationException e) { + throw new BangggoodException(ExceptionCode.USER_EMAIL_ALREADY_USED); + } } @Transactional - public AuthTokenResponse login(OauthLoginRequest request) { + public void withdraw(User user) { + userRepository.deleteById(user.getId()); + } + + @Transactional + public AuthTokenResponse oauthLogin(OauthLoginRequest request) { OauthInfoApiResponse oauthInfoApiResponse = oauthClient.requestOauthInfo(request); - User user = userRepository.findByEmail(oauthInfoApiResponse.kakao_account().email()) + User user = userRepository.findByEmailAndLoginType(new Email(oauthInfoApiResponse.kakao_account().email()), + LoginType.KAKAO) .orElseGet(() -> signUp(oauthInfoApiResponse)); String accessToken = jwtTokenProvider.createAccessToken(user); @@ -46,6 +64,24 @@ public AuthTokenResponse login(OauthLoginRequest request) { return AuthTokenResponse.of(accessToken, refreshToken); } + @Transactional(readOnly = true) + public AuthTokenResponse localLogin(LocalLoginRequestV1 request) { + User user = userRepository.findByEmailAndLoginType(new Email(request.email()), LoginType.LOCAL) + .orElseThrow(() -> new BangggoodException(ExceptionCode.USER_NOT_FOUND)); + checkPassword(request, user); + + String accessToken = jwtTokenProvider.createAccessToken(user); + String refreshToken = jwtTokenProvider.createRefreshToken(user); + return AuthTokenResponse.of(accessToken, refreshToken); + } + + + private void checkPassword(LocalLoginRequestV1 request, User user) { + if (user.isDifferent(request.password())) { + throw new BangggoodException(ExceptionCode.USER_INVALID_PASSWORD); + } + } + private User signUp(OauthInfoApiResponse oauthInfoApiResponse) { User user = userRepository.save(oauthInfoApiResponse.toUserEntity()); defaultChecklistService.createDefaultChecklistAndQuestions(user); @@ -54,7 +90,7 @@ private User signUp(OauthInfoApiResponse oauthInfoApiResponse) { @Transactional(readOnly = true) public User assignGuestUser() { - List foundGuestUser = userRepository.findUserByType(UserType.GUEST); + List foundGuestUser = userRepository.findUserByUserType(UserType.GUEST); if (foundGuestUser.size() > GUEST_USER_LIMIT) { throw new BangggoodException(ExceptionCode.GUEST_USER_UNEXPECTED_EXIST); @@ -68,34 +104,32 @@ public User assignGuestUser() { public void logout(String accessToken, String refreshToken, User user) { log.info("logout accessToken: {}", accessToken); log.info("logout refreshToken: {}", refreshToken); - AuthUser accessAuthUser = jwtTokenProvider.resolveToken(accessToken); - AuthUser refreshAuthUser = jwtTokenProvider.resolveToken(refreshToken); + AuthUser accessAuthUser = jwtTokenResolver.resolveAccessToken(accessToken); + AuthUser refreshAuthUser = jwtTokenResolver.resolveRefreshToken(refreshToken); validateTokenOwnership(user, accessAuthUser, refreshAuthUser); } - private static void validateTokenOwnership(User user, AuthUser accessAuthUser, AuthUser refreshAuthUser) { - if (!accessAuthUser.id().equals(refreshAuthUser.id())) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_USER_MISMATCH); - } - if (!user.getId().equals(accessAuthUser.id())) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_NOT_OWNED_BY_USER); - } - } - @Transactional(readOnly = true) public User getAuthUser(String token) { + AuthUser authUser = jwtTokenResolver.resolveAccessToken(token); log.info("extractUser token: {}", token); - AuthUser authUser = jwtTokenProvider.resolveToken(token); log.info("extractUser authUserId: {}", authUser.id()); return userRepository.getUserById(authUser.id()); } @Transactional(readOnly = true) - public String reIssueAccessToken(String refreshToken) { - jwtTokenProvider.validateRefreshTokenType(refreshToken); - AuthUser authUser = jwtTokenProvider.resolveToken(refreshToken); - + public String reissueAccessToken(String refreshToken) { + AuthUser authUser = jwtTokenResolver.resolveRefreshToken(refreshToken); User user = userRepository.getUserById(authUser.id()); return jwtTokenProvider.createAccessToken(user); } + + private static void validateTokenOwnership(User user, AuthUser accessAuthUser, AuthUser refreshAuthUser) { + if (!accessAuthUser.id().equals(refreshAuthUser.id())) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_USER_MISMATCH); + } + if (!user.getId().equals(accessAuthUser.id())) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_NOT_OWNED_BY_USER); + } + } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/JwtTokenProvider.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/JwtTokenProvider.java deleted file mode 100644 index 2257d5598..000000000 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/JwtTokenProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.bang_ggood.auth.service; - -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; -import com.bang_ggood.user.domain.User; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import java.util.Date; - -@Component -public class JwtTokenProvider { - - private static final String TOKEN_TYPE = "type"; - private final JwtTokenProperties jwtTokenProperties; - private final String secretKey; - - public JwtTokenProvider( - @Value("${jwt.secret-key}") String secretKey, - JwtTokenProperties jwtTokenProperties) { - this.secretKey = secretKey; - this.jwtTokenProperties = jwtTokenProperties; - } - - public String createAccessToken(User user) { - long accessTokenExpirationMillis = jwtTokenProperties.getAccessTokenExpirationMillis(); - return createToken(user, accessTokenExpirationMillis, TokenType.ACCESS_TOKEN); - } - - public String createRefreshToken(User user) { - long refreshTokenExpirationMillis = jwtTokenProperties.getRefreshTokenExpirationMillis(); - return createToken(user, refreshTokenExpirationMillis, TokenType.REFRESH_TOKEN); - } - - private String createToken(User user, long expirationMillis, TokenType tokenType) { - Date now = new Date(); - Date expiredDate = new Date(now.getTime() + expirationMillis); - - return Jwts.builder() - .setSubject(user.getId().toString()) - .setIssuedAt(now) - .setExpiration(expiredDate) - .claim(TOKEN_TYPE, tokenType.name()) - .signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) - .compact(); - } - - public AuthUser resolveToken(String token) { - try { - Claims claims = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) - .build() - .parseClaimsJws(token) - .getBody(); - - Long id = Long.valueOf(claims.getSubject()); - return AuthUser.from(id); - } catch (ExpiredJwtException exception) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_EXPIRED); - } catch (JwtException exception) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_INVALID); - } - } - - public void validateRefreshTokenType(String refreshToken) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes())) - .build() - .parseClaimsJws(refreshToken) - .getBody(); - - String tokenType = claims.get(TOKEN_TYPE, String.class); - - if (!tokenType.equals(TokenType.REFRESH_TOKEN.name())) { - throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_TYPE_MISMATCH); - } - } -} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/OauthClient.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/OauthClient.java deleted file mode 100644 index 42d66fe98..000000000 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/OauthClient.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.bang_ggood.auth.service; - -import com.bang_ggood.auth.dto.request.OauthLoginRequest; -import com.bang_ggood.auth.dto.response.OauthInfoApiResponse; -import com.bang_ggood.auth.dto.response.OauthTokenApiResponse; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestClient; - -@Component -public class OauthClient { - - private final RestClient restClient; - private final String tokenRequestUri; - private final String userInfoRequestUri; - private final String grantType; - private final String clientId; - private final String clientSecret; - private final String redirectUri; - - public OauthClient( - RestClient restClient, - @Value("${kakao.token_post_uri}") String tokenRequestUri, - @Value("${kakao.user_get_uri}") String userInfoRequestUri, - @Value("${kakao.grant_type}") String grantType, - @Value("${kakao.client_id}") String clientId, - @Value("${kakao.redirect_uri}") String redirectUrl, - @Value("${kakao.client_secret}") String clientSecret) { - this.restClient = restClient; - this.tokenRequestUri = tokenRequestUri; - this.userInfoRequestUri = userInfoRequestUri; - this.grantType = grantType; - this.clientId = clientId; - this.redirectUri = redirectUrl; - this.clientSecret = clientSecret; - } - - public OauthInfoApiResponse requestOauthInfo(OauthLoginRequest request) { - OauthTokenApiResponse oauthTokenApiResponse = requestToken(request); - - return restClient.get() - .uri(userInfoRequestUri) - .header("Authorization", "Bearer " + oauthTokenApiResponse.access_token()) - .retrieve() - .body(OauthInfoApiResponse.class); - } - - private OauthTokenApiResponse requestToken(OauthLoginRequest request) { - MultiValueMap map = new LinkedMultiValueMap<>(); - map.add("grant_type", grantType); - map.add("client_id", clientId); - map.add("redirect_uri", redirectUri); - map.add("code", request.code()); - map.add("client_secret", clientSecret); - - return restClient.post() - .uri(tokenRequestUri) - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body(map) - .retrieve() - .body(OauthTokenApiResponse.class); - } -} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/PasswordEncoder.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/PasswordEncoder.java new file mode 100644 index 000000000..8996642b3 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/PasswordEncoder.java @@ -0,0 +1,58 @@ +package com.bang_ggood.auth.service; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Base64; + +public class PasswordEncoder { + + private static final String DELIMITER = ":"; + private static final int PASSWORD_AND_SALT_LENGTH = 2; + + private PasswordEncoder() { + } + + public static String encodeWithGeneralSalt(String password) { + return encode(password, getSalt()); + } + + public static String encodeWithSpecificSalt(String password, String passwordWithSalt) { + return encode(password, extractSaltByPassword(passwordWithSalt)); + } + + private static String encode(String password, byte[] salt) { + try { + KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + byte[] hash = factory.generateSecret(spec).getEncoded(); + String encodedPassword = Base64.getEncoder().encodeToString(hash); + String encodedSalt = Base64.getEncoder().encodeToString(salt); + return encodedPassword + DELIMITER + encodedSalt; + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new BangggoodException(ExceptionCode.PASSWORD_HASHING_ERROR); + } + } + + private static byte[] getSalt() { + SecureRandom secureRandom = new SecureRandom(); + byte[] salt = new byte[64]; + secureRandom.nextBytes(salt); + return salt; + } + + private static byte[] extractSaltByPassword(String encodedPassword) { + String[] parts = encodedPassword.split(DELIMITER); + if (parts.length != PASSWORD_AND_SALT_LENGTH) { + throw new BangggoodException(ExceptionCode.PASSWORD_HASHING_ERROR); + } + + String encodedSalt = parts[1]; + return Base64.getDecoder().decode(encodedSalt); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/JwtTokenProperties.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenProperties.java similarity index 68% rename from backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/JwtTokenProperties.java rename to backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenProperties.java index 91427e8a2..dbca1ad1a 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/JwtTokenProperties.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenProperties.java @@ -1,18 +1,24 @@ -package com.bang_ggood.auth.service; +package com.bang_ggood.auth.service.jwt; +import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import javax.crypto.SecretKey; @Component public class JwtTokenProperties { + protected static final String TOKEN_TYPE = "type"; + + private final String secretKey; private final long accessTokenExpirationMillis; private final long refreshTokenExpirationMillis; public JwtTokenProperties( + @Value("${jwt.secret-key}") String secretKey, @Value("${jwt.accessToken-expiration-millis}") long accessTokenExpirationMillis, @Value("${jwt.refreshToken-expiration-millis}") long refreshTokenExpirationMillis) { - + this.secretKey = secretKey; this.accessTokenExpirationMillis = accessTokenExpirationMillis; this.refreshTokenExpirationMillis = refreshTokenExpirationMillis; } @@ -24,4 +30,8 @@ public long getAccessTokenExpirationMillis() { public long getRefreshTokenExpirationMillis() { return refreshTokenExpirationMillis; } + + public SecretKey getSecretKey() { + return Keys.hmacShaKeyFor(secretKey.getBytes()); + } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenProvider.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenProvider.java new file mode 100644 index 000000000..44db0bf47 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenProvider.java @@ -0,0 +1,38 @@ +package com.bang_ggood.auth.service.jwt; + +import com.bang_ggood.auth.service.TokenType; +import com.bang_ggood.user.domain.User; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import java.util.Date; + +@RequiredArgsConstructor +@Component +public class JwtTokenProvider { + + private final JwtTokenProperties jwtTokenProperties; + + public String createAccessToken(User user) { + long accessTokenExpirationMillis = jwtTokenProperties.getAccessTokenExpirationMillis(); + return createToken(user, accessTokenExpirationMillis, TokenType.ACCESS_TOKEN); + } + + public String createRefreshToken(User user) { + long refreshTokenExpirationMillis = jwtTokenProperties.getRefreshTokenExpirationMillis(); + return createToken(user, refreshTokenExpirationMillis, TokenType.REFRESH_TOKEN); + } + + private String createToken(User user, long expirationMillis, TokenType tokenType) { + Date now = new Date(); + Date expiredDate = new Date(now.getTime() + expirationMillis); + + return Jwts.builder() + .setSubject(user.getId().toString()) + .setIssuedAt(now) + .setExpiration(expiredDate) + .claim(JwtTokenProperties.TOKEN_TYPE, tokenType.name()) + .signWith(jwtTokenProperties.getSecretKey()) + .compact(); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenResolver.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenResolver.java new file mode 100644 index 000000000..cb38bb13c --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/jwt/JwtTokenResolver.java @@ -0,0 +1,53 @@ +package com.bang_ggood.auth.service.jwt; + +import com.bang_ggood.auth.service.AuthUser; +import com.bang_ggood.auth.service.TokenType; +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class JwtTokenResolver { + + private final JwtTokenProperties jwtTokenProperties; + + public AuthUser resolveAccessToken(String token) { + return resolveTokenByType(token, TokenType.ACCESS_TOKEN); + } + + public AuthUser resolveRefreshToken(String token) { + return resolveTokenByType(token, TokenType.REFRESH_TOKEN); + } + + private AuthUser resolveTokenByType(String token, TokenType tokenType) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(jwtTokenProperties.getSecretKey()) + .build() + .parseClaimsJws(token) + .getBody(); + + validateTokenType(claims, tokenType); + + Long id = Long.valueOf(claims.getSubject()); + return AuthUser.from(id); + } catch (ExpiredJwtException exception) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_EXPIRED); + } catch (JwtException exception) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_INVALID); + } + } + + private void validateTokenType(Claims claims, TokenType tokenType) { + String extractTokenType = claims.get(JwtTokenProperties.TOKEN_TYPE, String.class); + if (!extractTokenType.equals(tokenType.name())) { + throw new BangggoodException(ExceptionCode.AUTHENTICATION_TOKEN_TYPE_MISMATCH); + } + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/oauth/OauthClient.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/oauth/OauthClient.java new file mode 100644 index 000000000..2383df596 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/oauth/OauthClient.java @@ -0,0 +1,49 @@ +package com.bang_ggood.auth.service.oauth; + +import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.dto.response.OauthInfoApiResponse; +import com.bang_ggood.auth.dto.response.OauthTokenApiResponse; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; + +@Component +public class OauthClient { + + private final RestClient restClient; + private final OauthRequestProperties oauthRequestProperties; + + public OauthClient( + RestClient restClient, + OauthRequestProperties oauthRequestProperties) { + this.restClient = restClient; + this.oauthRequestProperties = oauthRequestProperties; + } + + public OauthInfoApiResponse requestOauthInfo(OauthLoginRequest request) { + OauthTokenApiResponse oauthTokenApiResponse = requestToken(request); + + String userInfoRequestUri = oauthRequestProperties.getUserInfoRequestUri(); + String headerName = "Authorization"; + String headerValue = "Bearer " + oauthTokenApiResponse.access_token(); + + return restClient.get() + .uri(userInfoRequestUri) + .header(headerName, headerValue) + .retrieve() + .body(OauthInfoApiResponse.class); + } + + private OauthTokenApiResponse requestToken(OauthLoginRequest request) { + String tokenRequestUri = oauthRequestProperties.getTokenRequestUri(); + MultiValueMap tokenRequestBody = oauthRequestProperties.createTokenRequestBody(request); + + return restClient.post() + .uri(tokenRequestUri) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(tokenRequestBody) + .retrieve() + .body(OauthTokenApiResponse.class); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/oauth/OauthRequestProperties.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/oauth/OauthRequestProperties.java new file mode 100644 index 000000000..bd55a12a8 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/oauth/OauthRequestProperties.java @@ -0,0 +1,76 @@ +package com.bang_ggood.auth.service.oauth; + +import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import java.util.Arrays; +import java.util.List; + +@Component +public class OauthRequestProperties { + + private final String tokenRequestUri; + private final String userInfoRequestUri; + private final String grantType; + private final String clientId; + private final String clientSecret; + private final List registeredRedirectUris; + + public OauthRequestProperties( + @Value("${kakao.token_post_uri}") String tokenPostUri, + @Value("${kakao.user_get_uri}") String userInfoRequestUri, + @Value("${kakao.grant_type}") String grantType, + @Value("${kakao.client_id}") String clientId, + @Value("${kakao.redirect_uris}") String registeredRedirectUris, + @Value("${kakao.client_secret}") String clientSecret) { + this.tokenRequestUri = tokenPostUri; + this.userInfoRequestUri = userInfoRequestUri; + this.grantType = grantType; + this.clientId = clientId; + this.registeredRedirectUris = convertToList(registeredRedirectUris); + this.clientSecret = clientSecret; + } + + private List convertToList(String registeredRedirectUris) { + return Arrays.stream(registeredRedirectUris.split(",")) + .map(String::trim) + .toList(); + } + + public MultiValueMap createTokenRequestBody(OauthLoginRequest request) { + String matchingRedirectUri = findMatchingRedirectUri(request.redirectUri()); + String code = request.code(); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("grant_type", grantType); + map.add("client_id", clientId); + map.add("redirect_uri", matchingRedirectUri); + map.add("code", code); + map.add("client_secret", clientSecret); + + return map; + } + + private String findMatchingRedirectUri(String redirectUri) { + return registeredRedirectUris.stream() + .filter(each -> each.equals(redirectUri)) + .findAny() + .orElseThrow(() -> new BangggoodException(ExceptionCode.OAUTH_REDIRECT_URI_MISMATCH)); + } + + public String getTokenRequestUri() { + return tokenRequestUri; + } + + public String getUserInfoRequestUri() { + return userInfoRequestUri; + } + + public List getRegisteredRedirectUris() { + return registeredRedirectUris; + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/controller/ChecklistController.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/controller/ChecklistController.java index d5ae5475e..8d0282e9b 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/controller/ChecklistController.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/controller/ChecklistController.java @@ -3,8 +3,10 @@ import com.bang_ggood.auth.config.AuthRequiredPrincipal; import com.bang_ggood.auth.config.UserPrincipal; import com.bang_ggood.checklist.dto.request.ChecklistRequest; -import com.bang_ggood.checklist.dto.response.SelectedChecklistResponse; +import com.bang_ggood.checklist.dto.request.ChecklistRequestV1; import com.bang_ggood.checklist.dto.response.ChecklistsPreviewResponse; +import com.bang_ggood.checklist.dto.response.SelectedChecklistResponse; +import com.bang_ggood.checklist.dto.response.SelectedChecklistResponseV1; import com.bang_ggood.checklist.service.ChecklistManageService; import com.bang_ggood.checklist.service.ChecklistService; import com.bang_ggood.user.domain.User; @@ -34,7 +36,14 @@ public ChecklistController(ChecklistManageService checklistManageService, Checkl public ResponseEntity createChecklist(@AuthRequiredPrincipal User user, @Valid @RequestBody ChecklistRequest checklistRequest) { long checklistId = checklistManageService.createChecklist(user, checklistRequest); - return ResponseEntity.created(URI.create("/checklists/" + checklistId)).build(); + return ResponseEntity.created(URI.create("/checklist/" + checklistId)).build(); + } + + @PostMapping("/v1/checklists") + public ResponseEntity createChecklistV1(@AuthRequiredPrincipal User user, + @Valid @RequestBody ChecklistRequestV1 checklistRequestV1) { + long checklistId = checklistManageService.createChecklistV1(user, checklistRequestV1); + return ResponseEntity.created(URI.create("/checklist/" + checklistId)).build(); } @GetMapping("/checklists/{id}") @@ -43,6 +52,12 @@ public ResponseEntity readChecklistById(@UserPrincipa return ResponseEntity.ok(checklistManageService.readChecklist(user, checklistId)); } + @GetMapping("v1/checklists/{id}") + public ResponseEntity readChecklistByIdV1(@UserPrincipal User user, + @PathVariable("id") Long checklistId) { + return ResponseEntity.ok(checklistManageService.readChecklistV1(user, checklistId)); + } + @GetMapping("/checklists") public ResponseEntity readChecklistsPreview(@UserPrincipal User user) { return ResponseEntity.ok(checklistManageService.readAllChecklistsPreview(user)); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/domain/Checklist.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/domain/Checklist.java index 330eeef96..4c88eac62 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/domain/Checklist.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/domain/Checklist.java @@ -20,9 +20,15 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.List; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class Checklist extends BaseEntity { @@ -61,7 +67,6 @@ public class Checklist extends BaseEntity { @OneToMany(mappedBy = "checklist") private List questions; - public Checklist(Room room, User user, Integer deposit, Integer rent, Integer maintenanceFee, Integer contractTerm, OccupancyMonth occupancyMonth, OccupancyPeriod occupancyPeriod, String realEstate, String memo, String summary) { @@ -79,16 +84,6 @@ public Checklist(Room room, User user, Integer deposit, Integer rent, Integer ma validateMemoLength(); } - public Checklist(Integer deposit, Integer rent, Integer maintenanceFee, Integer contractTerm, - OccupancyMonth occupancyMonth, OccupancyPeriod occupancyPeriod, String realEstate, - String memo, String summary) { - this(null, null, deposit, rent, maintenanceFee, contractTerm, occupancyMonth, occupancyPeriod, realEstate, memo, - summary); - } - - protected Checklist() { - } - public boolean isOwnedBy(User user) { return this.user.equals(user); } @@ -113,44 +108,8 @@ private void validateMemoLength() { } } - public Long getId() { - return id; - } - - public Room getRoom() { - return room; - } - - public User getUser() { - return user; - } - - public Integer getDeposit() { - return deposit; - } - - public Integer getRent() { - return rent; - } - - public Integer getMaintenanceFee() { - return maintenanceFee; - } - - public Integer getContractTerm() { - return contractTerm; - } - - public String getRealEstate() { - return realEstate; - } - - public String getMemo() { - return memo; - } - - public String getSummary() { - return summary; + public Long getRoomId() { + return room.getId(); } public String getRoomName() { @@ -197,10 +156,6 @@ public String getOccupancyPeriod() { return occupancyPeriod.getPeriod(); } - public List getQuestions() { - return questions; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/dto/request/ChecklistRequestV1.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/dto/request/ChecklistRequestV1.java new file mode 100644 index 000000000..03edc011b --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/dto/request/ChecklistRequestV1.java @@ -0,0 +1,16 @@ +package com.bang_ggood.checklist.dto.request; + +import com.bang_ggood.question.dto.request.QuestionRequest; +import com.bang_ggood.room.dto.request.RoomRequest; +import com.bang_ggood.station.dto.request.ChecklistStationRequest; +import jakarta.validation.Valid; +import java.util.List; + +public record ChecklistRequestV1(@Valid RoomRequest room, List options, + @Valid List questions, + ChecklistStationRequest geolocation) { + + public ChecklistRequest toChecklistRequest() { + return new ChecklistRequest(room, options, questions); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/dto/response/SelectedChecklistResponseV1.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/dto/response/SelectedChecklistResponseV1.java new file mode 100644 index 000000000..07da6e8f2 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/dto/response/SelectedChecklistResponseV1.java @@ -0,0 +1,22 @@ +package com.bang_ggood.checklist.dto.response; + +import com.bang_ggood.option.dto.response.SelectedOptionResponse; +import com.bang_ggood.question.dto.response.SelectedCategoryQuestionsResponse; +import com.bang_ggood.room.dto.response.SelectedRoomResponse; +import com.bang_ggood.room.dto.response.SelectedRoomResponseV1; +import com.bang_ggood.station.dto.response.SubwayStationResponses; +import java.util.List; + +public record SelectedChecklistResponseV1(SelectedRoomResponseV1 room, + List options, + List categories, + boolean isLiked, + SubwayStationResponses stations) { + + public static SelectedChecklistResponseV1 of(SelectedRoomResponse room, List options, + List categories, boolean isLiked, + SubwayStationResponses stations) { + return new SelectedChecklistResponseV1(SelectedRoomResponseV1.from(room), options, categories, isLiked, + stations); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/repository/ChecklistRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/repository/ChecklistRepository.java index 918e8b746..0ec533a9b 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/repository/ChecklistRepository.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/repository/ChecklistRepository.java @@ -28,7 +28,7 @@ default Checklist getById(@Param("id") Long id) { @Query("SELECT c FROM Checklist c " + "WHERE c.user = :user " + "AND c.deleted = false " - + "ORDER BY c.createdAt DESC ") + + "ORDER BY c.createdAt DESC, c.id DESC ") List findAllByUserOrderByLatest(@Param("user") User user); @Query("SELECT c FROM Checklist c " diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistManageService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistManageService.java index c2896dfca..066eed3b6 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistManageService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistManageService.java @@ -2,11 +2,12 @@ import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.dto.request.ChecklistRequest; +import com.bang_ggood.checklist.dto.request.ChecklistRequestV1; import com.bang_ggood.checklist.dto.response.ChecklistPreviewResponse; import com.bang_ggood.checklist.dto.response.ChecklistsPreviewResponse; import com.bang_ggood.checklist.dto.response.SelectedChecklistResponse; +import com.bang_ggood.checklist.dto.response.SelectedChecklistResponseV1; import com.bang_ggood.like.service.ChecklistLikeService; -import com.bang_ggood.checklist.dto.response.SelectedChecklistResponse; import com.bang_ggood.maintenance.domain.ChecklistMaintenance; import com.bang_ggood.maintenance.domain.MaintenanceItem; import com.bang_ggood.maintenance.service.ChecklistMaintenanceService; @@ -14,22 +15,28 @@ import com.bang_ggood.option.dto.response.SelectedOptionResponse; import com.bang_ggood.option.service.ChecklistOptionService; import com.bang_ggood.question.domain.Answer; -import com.bang_ggood.question.domain.Category; +import com.bang_ggood.question.domain.CategoryEntity; import com.bang_ggood.question.domain.ChecklistQuestion; import com.bang_ggood.question.domain.Question; import com.bang_ggood.question.dto.response.SelectedCategoryQuestionsResponse; import com.bang_ggood.question.dto.response.SelectedQuestionResponse; import com.bang_ggood.question.service.ChecklistQuestionService; +import com.bang_ggood.question.service.QuestionService; import com.bang_ggood.room.domain.Room; import com.bang_ggood.room.dto.response.SelectedRoomResponse; import com.bang_ggood.room.service.RoomService; +import com.bang_ggood.station.domain.ChecklistStation; +import com.bang_ggood.station.dto.request.ChecklistStationRequest; +import com.bang_ggood.station.dto.response.SubwayStationResponse; +import com.bang_ggood.station.dto.response.SubwayStationResponses; +import com.bang_ggood.station.service.ChecklistStationService; import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; +@RequiredArgsConstructor @Service public class ChecklistManageService { @@ -39,27 +46,29 @@ public class ChecklistManageService { private final ChecklistQuestionService checklistQuestionService; private final ChecklistMaintenanceService checklistMaintenanceService; private final ChecklistLikeService checklistLikeService; + private final ChecklistStationService checklistStationService; + private final QuestionService questionService; - public ChecklistManageService(RoomService roomService, ChecklistService checklistService, - ChecklistOptionService checklistOptionService, - ChecklistQuestionService checklistQuestionService, - ChecklistMaintenanceService checklistMaintenanceService, - ChecklistLikeService checklistLikeService) { - this.roomService = roomService; - this.checklistService = checklistService; - this.checklistOptionService = checklistOptionService; - this.checklistQuestionService = checklistQuestionService; - this.checklistMaintenanceService = checklistMaintenanceService; - this.checklistLikeService = checklistLikeService; + @Transactional + public Long createChecklist(User user, ChecklistRequest checklistRequest) { + Room room = roomService.createRoom(checklistRequest.toRoomEntity()); + Checklist checklist = checklistService.createChecklist(checklistRequest.toChecklistEntity(room, user)); + createChecklistOptions(checklistRequest, checklist); + createChecklistQuestions(checklistRequest, checklist); + createChecklistMaintenances(checklistRequest, checklist); + return checklist.getId(); } @Transactional - public Long createChecklist(User user, ChecklistRequest checklistRequest) { + public Long createChecklistV1(User user, ChecklistRequestV1 checklistRequestV1) { + ChecklistRequest checklistRequest = checklistRequestV1.toChecklistRequest(); + Room room = roomService.createRoom(checklistRequest.toRoomEntity()); Checklist checklist = checklistService.createChecklist(checklistRequest.toChecklistEntity(room, user)); createChecklistOptions(checklistRequest, checklist); createChecklistQuestions(checklistRequest, checklist); createChecklistMaintenances(checklistRequest, checklist); + createChecklistStation(checklistRequestV1, checklist); return checklist.getId(); } @@ -75,6 +84,7 @@ private void createChecklistQuestions(ChecklistRequest checklistRequest, Checkli .map(question -> new ChecklistQuestion( checklist, Question.fromId(question.questionId()), + questionService.readQuestion(question.questionId()), Answer.from(question.answer()))) .toList(); checklistQuestionService.createQuestions(checklistQuestions); @@ -89,6 +99,11 @@ private void createChecklistMaintenances(ChecklistRequest checklistRequest, Chec checklistMaintenanceService.createMaintenances(checklistMaintenances); } + private void createChecklistStation(ChecklistRequestV1 checklistRequestV1, Checklist checklist) { + ChecklistStationRequest geolocation = checklistRequestV1.geolocation(); + checklistStationService.createChecklistStations(checklist, geolocation.latitude(), geolocation.latitude()); + } + @Transactional(readOnly = true) public SelectedChecklistResponse readChecklist(User user, Long checklistId) { Checklist checklist = checklistService.readChecklist(user, checklistId); @@ -102,6 +117,20 @@ public SelectedChecklistResponse readChecklist(User user, Long checklistId) { return SelectedChecklistResponse.of(room, options, questions, isLiked); } + @Transactional(readOnly = true) + public SelectedChecklistResponseV1 readChecklistV1(User user, Long checklistId) { + Checklist checklist = checklistService.readChecklist(user, checklistId); + + List maintenances = readChecklistMaintenances(checklist); + List options = readChecklistOptions(checklist); + List questions = readChecklistQuestions(checklist); + SelectedRoomResponse room = SelectedRoomResponse.of(checklist, maintenances); + boolean isLiked = checklistLikeService.isLikedChecklist(checklist); + SubwayStationResponses stations = readChecklistStations(checklist); + + return SelectedChecklistResponseV1.of(room, options, questions, isLiked, stations); + } + private List readChecklistMaintenances(Checklist checklist) { return checklistMaintenanceService.readChecklistMaintenances(checklist) .stream() @@ -119,21 +148,31 @@ private List readChecklistOptions(Checklist checklist) { private List readChecklistQuestions(Checklist checklist) { List checklistQuestions = checklistQuestionService.readChecklistQuestions(checklist); - return Arrays.stream(Category.values()) + return questionService.findAllCategories().stream() .map(category -> categorizeChecklistQuestions(category, checklistQuestions)) + .filter(selectedCategoryQuestionsResponse -> !selectedCategoryQuestionsResponse.questions().isEmpty()) .toList(); } - private SelectedCategoryQuestionsResponse categorizeChecklistQuestions(Category category, + private SelectedCategoryQuestionsResponse categorizeChecklistQuestions(CategoryEntity category, List checklistQuestions) { - List selectedQuestionResponse = Question.filter(category, checklistQuestions) + List selectedQuestionResponse = checklistQuestionService.categorizeChecklistQuestions(category, checklistQuestions) .stream() - .map(SelectedQuestionResponse::new) + .map(checklistQuestion -> new SelectedQuestionResponse(checklistQuestion, questionService.readHighlights(checklistQuestion.getQuestionId()))) .toList(); return SelectedCategoryQuestionsResponse.of(category, selectedQuestionResponse); } + private SubwayStationResponses readChecklistStations(Checklist checklist) { + List checklistStations = checklistStationService.readChecklistStationsByChecklist(checklist); + List stations = checklistStations.stream() + .map(SubwayStationResponse::from) + .toList(); + + return SubwayStationResponses.from(stations); + } + @Transactional(readOnly = true) public ChecklistsPreviewResponse readLikedChecklistsPreview(User user) { List likedChecklists = checklistService.readLikedChecklistsPreview(user); @@ -155,7 +194,7 @@ public void deleteChecklistById(User user, Long id) { checklistOptionService.deleteAllByChecklistId(checklist.getId()); checklistMaintenanceService.deleteAllByChecklistId(checklist.getId()); checklistService.deleteById(id); - roomService.deleteById(checklist.getRoom().getId()); + roomService.deleteById(checklist.getRoomId()); } @Transactional(readOnly = true) @@ -198,6 +237,7 @@ private void updateChecklistQuestions(ChecklistRequest checklistRequest, Checkli .map(question -> new ChecklistQuestion( checklist, Question.fromId(question.questionId()), + questionService.readQuestion(question.questionId()), Answer.from(question.answer()))) .toList(); checklistQuestionService.updateQuestions(questions, updateQuestions); @@ -209,7 +249,7 @@ private void updateChecklistMaintenances(ChecklistRequest checklistRequest, Chec checklistRequest.room().includedMaintenances().stream() .map(maintenanceId -> new ChecklistMaintenance(checklist, MaintenanceItem.fromId(maintenanceId))) - .collect(Collectors.toList()); + .toList(); checklistMaintenanceService.updateMaintenances(checklist.getId(), checklistMaintenances); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistService.java index b6eaece69..7bb755b36 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/checklist/service/ChecklistService.java @@ -4,39 +4,17 @@ import com.bang_ggood.checklist.repository.ChecklistRepository; import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; -import com.bang_ggood.like.domain.ChecklistLike; -import com.bang_ggood.like.repository.ChecklistLikeRepository; -import com.bang_ggood.maintenance.repository.ChecklistMaintenanceRepository; -import com.bang_ggood.option.repository.ChecklistOptionRepository; -import com.bang_ggood.question.repository.ChecklistQuestionRepository; -import com.bang_ggood.room.repository.RoomRepository; import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +@RequiredArgsConstructor @Service public class ChecklistService { private final ChecklistRepository checklistRepository; - private final RoomRepository roomRepository; - private final ChecklistOptionRepository checklistOptionRepository; - private final ChecklistQuestionRepository checklistQuestionRepository; - private final ChecklistMaintenanceRepository checklistMaintenanceRepository; - private final ChecklistLikeRepository checklistLikeRepository; - - public ChecklistService(ChecklistRepository checklistRepository, RoomRepository roomRepository, - ChecklistOptionRepository checklistOptionRepository, - ChecklistQuestionRepository checklistQuestionRepository, - ChecklistMaintenanceRepository checklistMaintenanceRepository, - ChecklistLikeRepository checklistLikeRepository) { - this.checklistRepository = checklistRepository; - this.roomRepository = roomRepository; - this.checklistOptionRepository = checklistOptionRepository; - this.checklistQuestionRepository = checklistQuestionRepository; - this.checklistMaintenanceRepository = checklistMaintenanceRepository; - this.checklistLikeRepository = checklistLikeRepository; - } @Transactional public Checklist createChecklist(Checklist checklist) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/DBInitializer.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/DBInitializer.java index a09fe9563..bfa06e0d1 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/DBInitializer.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/DBInitializer.java @@ -1,5 +1,6 @@ package com.bang_ggood.global; +import com.bang_ggood.user.domain.LoginType; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.domain.UserType; import com.bang_ggood.user.service.UserService; @@ -27,7 +28,7 @@ public void createGuestUser() { List foundGuestUser = userService.readUser(UserType.GUEST); if (foundGuestUser.isEmpty()) { - User guestUser = new User("방끗", "bang-ggood@gmail.com", UserType.GUEST); + User guestUser = new User("방끗", "bang-ggood1@gmail.com", UserType.GUEST, LoginType.LOCAL); userService.createUser(guestUser); defaultChecklistService.createDefaultChecklistAndQuestions(guestUser); } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/CorsConfig.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/CorsConfig.java index 72bff4801..8cdcdc547 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/CorsConfig.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/CorsConfig.java @@ -3,6 +3,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @@ -21,6 +22,7 @@ public CorsFilter corsFilter() { configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.addAllowedHeader("*"); configuration.setAllowCredentials(true); + configuration.addExposedHeader(HttpHeaders.LOCATION); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/WebMvcConfig.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/WebMvcConfig.java index 3ed37853e..2caa09c3e 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/WebMvcConfig.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/config/WebMvcConfig.java @@ -1,8 +1,8 @@ package com.bang_ggood.global.config; import com.bang_ggood.auth.config.AuthRequiredPrincipalArgumentResolver; -import com.bang_ggood.auth.controller.CookieResolver; import com.bang_ggood.auth.config.UserPrincipalArgumentResolver; +import com.bang_ggood.auth.controller.cookie.CookieResolver; import com.bang_ggood.auth.service.AuthService; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/BangggoodException.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/BangggoodException.java index 3b0928f57..d6bc74dbc 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/BangggoodException.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/BangggoodException.java @@ -18,4 +18,8 @@ public String getMessage() { public HttpStatusCode getHttpStatusCode() { return exceptionCode.getHttpStatus(); } + + public String getClientExceptionCodeName() { + return exceptionCode.getClientExceptionCode().name(); + } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ClientExceptionCode.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ClientExceptionCode.java new file mode 100644 index 000000000..f3ba35200 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ClientExceptionCode.java @@ -0,0 +1,32 @@ +package com.bang_ggood.global.exception; + +public enum ClientExceptionCode { + + ARTICLE_NOT_FOUND, + AUTH_ACCESS_TOKEN_EMPTY, + AUTH_TOKEN_EMPTY, + AUTH_TOKEN_INVALID, + CHECKLIST_ERROR, + CHECKLIST_NOT_FOUND, + CHECKLIST_SERVER_ERROR, + CUSTOM_ERROR, + INTERNAL_SERVER_ERROR, + OAUTH_SERVER_ERROR, + PASSWORD_HASHING_ERROR, + QUESTION_ERROR, + STATION_SERVER_ERROR, + UNAUTH_ERROR, + USER_EMAIL_ALREADY_USED, + USER_INVALID_FORMAT, + USER_NOT_FOUND, + LOGIN_ERROR, + INVALID_PARAMETER, + + // TODO: 임의 사용 지워질 코드 + AUTH_TOKEN_NOT_OWNED_BY_USER, + AUTH_TOKEN_USER_MISMATCH, + GUEST_USER_NOT_FOUND, + GUEST_USER_UNEXPECTED_EXIST, + LIKE_ALREADY_EXISTS, + LIKE_NOT_EXISTS, +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ExceptionCode.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ExceptionCode.java index 20da81e03..0af933495 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ExceptionCode.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/ExceptionCode.java @@ -1,95 +1,98 @@ package com.bang_ggood.global.exception; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter public enum ExceptionCode { // 전체 - INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "잘못된 인자입니다."), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.INTERNAL_SERVER_ERROR, "예상치 못한 서버에러가 발생했습니다"), + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, ClientExceptionCode.INVALID_PARAMETER, "잘못된 인자입니다."), // Option - OPTION_INVALID(HttpStatus.BAD_REQUEST, "잘못된 옵션 ID입니다."), - OPTION_DUPLICATED(HttpStatus.BAD_REQUEST, "중복된 옵션이 존재합니다."), + OPTION_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "잘못된 옵션 ID입니다."), + OPTION_DUPLICATED(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "중복된 옵션이 존재합니다."), // Question - QUESTION_ID_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "중복된 질문 ID가 존재해 질문을 생성할 수 없습니다."), - QUESTION_HIGHLIGHT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "잘못된 하이라이트 키워드가 존재해 질문을 생성할 수 없습니다."), - QUESTION_INVALID(HttpStatus.BAD_REQUEST, "잘못된 질문 ID입니다."), - QUESTION_DUPLICATED(HttpStatus.BAD_REQUEST, "중복된 질문이 존재합니다."), - QUESTION_DIFFERENT(HttpStatus.BAD_REQUEST, "수정할 질문 목록이 기존의 질문 목록과 동일하지 않습니다."), + QUESTION_ID_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.QUESTION_ERROR, "중복된 질문 ID가 존재해 질문을 생성할 수 없습니다."), + QUESTION_HIGHLIGHT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.CHECKLIST_SERVER_ERROR, "잘못된 하이라이트 키워드가 존재해 질문을 생성할 수 없습니다."), + QUESTION_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.QUESTION_ERROR, "잘못된 질문 ID입니다."), + QUESTION_DUPLICATED(HttpStatus.BAD_REQUEST, ClientExceptionCode.QUESTION_ERROR, "중복된 질문이 존재합니다."), + QUESTION_DIFFERENT(HttpStatus.BAD_REQUEST, ClientExceptionCode.QUESTION_ERROR, "수정할 질문 목록이 기존의 질문 목록과 동일하지 않습니다."), // User - USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "유저가 존재하지 않습니다."), - GUEST_USER_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "게스트 유저가 존재하지 않습니다."), - GUEST_USER_UNEXPECTED_EXIST(HttpStatus.INTERNAL_SERVER_ERROR, "예상치 못한 게스트 유저가 존재합니다. 데이터베이스를 확인해주세요."), + USER_NOT_FOUND(HttpStatus.UNAUTHORIZED, ClientExceptionCode.USER_NOT_FOUND, "유저가 존재하지 않습니다."), + USER_INVALID_PASSWORD(HttpStatus.BAD_REQUEST, ClientExceptionCode.LOGIN_ERROR, "비밀번호가 일치하지 않습니다."), + USER_EMAIL_ALREADY_USED(HttpStatus.CONFLICT, ClientExceptionCode.USER_EMAIL_ALREADY_USED, "이미 해당 이메일을 사용하는 유저가 존재합니다."), + GUEST_USER_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.GUEST_USER_NOT_FOUND, "게스트 유저가 존재하지 않습니다."), + GUEST_USER_UNEXPECTED_EXIST(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.GUEST_USER_UNEXPECTED_EXIST, "예상치 못한 게스트 유저가 존재합니다. 데이터베이스를 확인해주세요."), + + //Email + EMAIL_INVALID_FORMAT(HttpStatus.BAD_REQUEST, ClientExceptionCode.USER_INVALID_FORMAT, "유효하지 않은 이메일 형식입니다."), + + //Password + PASSWORD_INVALID_FORMAT(HttpStatus.BAD_REQUEST, ClientExceptionCode.USER_INVALID_FORMAT, "유효하지 않은 비밀번호 형식입니다."), + PASSWORD_HASHING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.PASSWORD_HASHING_ERROR, "비밀번호 해싱 중 오류가 발생했습니다."), // Answer - ANSWER_INVALID(HttpStatus.BAD_REQUEST, "점수가 유효하지 않습니다."), + ANSWER_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "답변이 유효하지 않습니다."), // Checklist - CHECKLIST_COMPARISON_INVALID_COUNT(HttpStatus.BAD_REQUEST, "비교할 체크리스트 개수가 유효하지 않습니다."), - CHECKLIST_NOT_FOUND(HttpStatus.BAD_REQUEST, "체크리스트가 존재하지 않습니다."), - CHECKLIST_MEMO_INVALID_LENGTH(HttpStatus.BAD_REQUEST, "체크리스트 메모는 1000자 이하여야 합니다."), - CHECKLIST_NOT_OWNED_BY_USER(HttpStatus.BAD_REQUEST, "유저의 체크리스트가 아닙니다."), + CHECKLIST_NOT_FOUND(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_NOT_FOUND, "체크리스트가 존재하지 않습니다."), + CHECKLIST_MEMO_INVALID_LENGTH(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "체크리스트 메모는 1000자 이하여야 합니다."), + CHECKLIST_NOT_OWNED_BY_USER(HttpStatus.UNAUTHORIZED, ClientExceptionCode.UNAUTH_ERROR, "유저의 체크리스트가 아닙니다."), // CustomChecklist - CUSTOM_CHECKLIST_QUESTION_EMPTY(HttpStatus.BAD_REQUEST, "커스텀 질문 개수가 유효하지 않습니다."), + CUSTOM_CHECKLIST_QUESTION_EMPTY(HttpStatus.BAD_REQUEST, ClientExceptionCode.CUSTOM_ERROR, "커스텀 질문 개수가 유효하지 않습니다."), // FloorLevel - FLOOR_LEVEL_INVALID(HttpStatus.BAD_REQUEST, "층 종류가 유효하지 않습니다."), + FLOOR_LEVEL_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "층 종류가 유효하지 않습니다."), // Structure - STRUCTURE_INVALID(HttpStatus.BAD_REQUEST, "방 구조가 유효하지 않습니다."), + STRUCTURE_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "방 구조가 유효하지 않습니다."), // Room - ROOM_FLOOR_AND_LEVEL_INVALID(HttpStatus.BAD_REQUEST, "방이 지상층일 경우에만 층수를 입력할 수 있습니다."), + ROOM_FLOOR_AND_LEVEL_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "방이 지상층일 경우에만 층수를 입력할 수 있습니다."), + // OccupancyMonth - OCCUPANCY_MONTH_INVALID(HttpStatus.BAD_REQUEST, "입주 가능월은 1부터 12 사이 혹은 null 값만 가능합니다."), + OCCUPANCY_MONTH_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "입주 가능월은 1부터 12 사이 혹은 null 값만 가능합니다."), // OccupancyPeriod - OCCUPANCY_PERIOD_INVALID(HttpStatus.BAD_REQUEST, "입주 가능 기간은 초, 중, 말 혹은 null 값만 가능합니다."), + OCCUPANCY_PERIOD_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "입주 가능 기간은 초, 중, 말 혹은 null 값만 가능합니다."), // MaintenanceItem - MAINTENANCE_ITEM_DUPLICATE(HttpStatus.BAD_REQUEST, "중복된 관리비 항목이 존재합니다."), - MAINTENANCE_ITEM_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 관리비 항목이 입력되었습니다."), - - //like - LIKE_ALREADY_EXISTS(HttpStatus.CONFLICT, "체크리스트가 이미 좋아요 상태입니다"), - LIKE_NOT_EXISTS(HttpStatus.BAD_REQUEST, "체크리스트 좋아요가 존재하지 않아 삭제할 수 없습니다."), + MAINTENANCE_ITEM_DUPLICATE(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "중복된 관리비 항목이 존재합니다."), + MAINTENANCE_ITEM_INVALID(HttpStatus.BAD_REQUEST, ClientExceptionCode.CHECKLIST_ERROR, "유효하지 않은 관리비 항목이 입력되었습니다."), // Auth - AUTHENTICATION_COOKIE_EMPTY(HttpStatus.UNAUTHORIZED, "인증정보가 존재하지 않습니다. 쿠키값을 넣어주세요."), - AUTHENTICATION_REQUIRED_TOKEN_EMPTY(HttpStatus.UNAUTHORIZED, "토큰 정보가 존재하지 않습니다. 토큰을 재발급해주세요."), - AUTHENTICATION_TOKEN_EMPTY(HttpStatus.UNAUTHORIZED, "로그인이 필요한 사용자입니다."), - AUTHENTICATION_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다."), - AUTHENTICATION_TOKEN_INVALID(HttpStatus.UNAUTHORIZED, "토큰 정보가 올바르지 않습니다."), - AUTHENTICATION_TOKEN_IN_BLACKLIST(HttpStatus.UNAUTHORIZED, "이미 로그아웃되어 더 이상 사용할 수 없는 토큰입니다. 다시 로그인 해주세요."), - AUTHENTICATION_TOKEN_NOT_OWNED_BY_USER(HttpStatus.UNAUTHORIZED, "해당 유저의 토큰이 아닙니다"), - AUTHENTICATION_TOKEN_USER_MISMATCH(HttpStatus.UNAUTHORIZED, "엑세스 토큰과 리프레시 토큰의 소유자가 다릅니다."), - AUTHENTICATION_TOKEN_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "토큰의 타입이 올바르지 않습니다. 리프레시 토큰을 보내주세요."), - OAUTH_TOKEN_INTERNAL_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "카카오 서버와 통신하는 과정 중 예상치 못한 예외가 발생했습니다."), + AUTHENTICATION_ACCESS_TOKEN_EMPTY(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_ACCESS_TOKEN_EMPTY, "액세스 토큰이 존재하지 않습니다. 액세스 토큰을 발급해주세요."), + AUTHENTICATION_REFRESH_TOKEN_EMPTY(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_TOKEN_EMPTY, "리프레시 토큰이 존재하지 않습니다. 다시 로그인해주세요."), + AUTHENTICATION_TOKEN_EMPTY(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_TOKEN_EMPTY, "로그인이 필요한 사용자입니다."), + AUTHENTICATION_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_TOKEN_INVALID, "토큰이 만료되었습니다."), + AUTHENTICATION_TOKEN_INVALID(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_TOKEN_INVALID, "토큰 정보가 올바르지 않습니다."), + AUTHENTICATION_TOKEN_NOT_OWNED_BY_USER(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_TOKEN_NOT_OWNED_BY_USER, "해당 유저의 토큰이 아닙니다."), + AUTHENTICATION_TOKEN_USER_MISMATCH(HttpStatus.UNAUTHORIZED, ClientExceptionCode.AUTH_TOKEN_USER_MISMATCH, "엑세스 토큰과 리프레시 토큰의 소유자가 다릅니다."), + AUTHENTICATION_TOKEN_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, ClientExceptionCode.AUTH_TOKEN_INVALID, "토큰 타입이 올바르지 않습니다."), + OAUTH_TOKEN_INTERNAL_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.OAUTH_SERVER_ERROR, "카카오 서버와 통신하는 과정 중 예상치 못한 예외가 발생했습니다."), + OAUTH_REDIRECT_URI_MISMATCH(HttpStatus.BAD_REQUEST, ClientExceptionCode.OAUTH_SERVER_ERROR, "일치하는 Redirect URI가 존재하지 않습니다."), + // Article - ARTICLE_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당 아티클이 존재하지 않습니다."), + ARTICLE_NOT_FOUND(HttpStatus.BAD_REQUEST, ClientExceptionCode.ARTICLE_NOT_FOUND, "해당 아티클이 존재하지 않습니다."), // Station - STATION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "지하철 역을 찾을 수 없습니다."), - STATION_NAME_NOT_SAME(HttpStatus.INTERNAL_SERVER_ERROR, "지하철 역을 찾을 수 없습니다."); + STATION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.STATION_SERVER_ERROR, "지하철 역을 찾을 수 없습니다."), + STATION_NAME_NOT_SAME(HttpStatus.INTERNAL_SERVER_ERROR, ClientExceptionCode.STATION_SERVER_ERROR, "지하철 역을 찾을 수 없습니다."); private final HttpStatus httpStatus; + private final ClientExceptionCode clientExceptionCode; private final String message; - ExceptionCode(HttpStatus httpStatus, String message) { + ExceptionCode(HttpStatus httpStatus, ClientExceptionCode clientExceptionCode, String message) { this.httpStatus = httpStatus; + this.clientExceptionCode = clientExceptionCode; this.message = message; } - - public HttpStatus getHttpStatus() { - return httpStatus; - } - - public String getMessage() { - return message; - } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/dto/ExceptionResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/dto/ExceptionResponse.java index 188730202..94d221cf0 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/dto/ExceptionResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/exception/dto/ExceptionResponse.java @@ -1,4 +1,4 @@ package com.bang_ggood.global.exception.dto; -public record ExceptionResponse(String httpMethod, String path, String message) { +public record ExceptionResponse(String httpMethod, String path, String bangggoodCode, String message) { } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/global/handler/GlobalExceptionHandler.java b/backend/bang-ggood/src/main/java/com/bang_ggood/global/handler/GlobalExceptionHandler.java index 782a3a6da..2eb472690 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/global/handler/GlobalExceptionHandler.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/global/handler/GlobalExceptionHandler.java @@ -1,12 +1,14 @@ package com.bang_ggood.global.handler; import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.global.exception.OauthException; import com.bang_ggood.global.exception.dto.ExceptionResponse; import com.bang_ggood.global.exception.dto.OauthExceptionResponse; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -20,6 +22,7 @@ public ResponseEntity handleBangggoodException(BangggoodExcep ExceptionResponse response = new ExceptionResponse( request.getMethod(), request.getRequestURI(), + exception.getClientExceptionCodeName(), exception.getMessage()); return ResponseEntity.status(exception.getHttpStatusCode()) @@ -32,7 +35,8 @@ public ResponseEntity handleRuntimeException(RuntimeException ExceptionResponse response = new ExceptionResponse( request.getMethod(), request.getRequestURI(), - "예상치 못한 서버에러가 발생했습니다."); + ExceptionCode.INTERNAL_SERVER_ERROR.getClientExceptionCode().name(), + ExceptionCode.INTERNAL_SERVER_ERROR.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(response); } @@ -41,10 +45,17 @@ public ResponseEntity handleRuntimeException(RuntimeException public ResponseEntity handleMethodArgumentNotValidException( MethodArgumentNotValidException exception, HttpServletRequest request) { + FieldError fieldError = exception.getFieldError(); + String errorMessage = ExceptionCode.INVALID_PARAMETER.getMessage(); // TODO 리팩터링 필요 + if (fieldError != null) { + errorMessage = fieldError.getDefaultMessage(); + } + ExceptionResponse response = new ExceptionResponse( request.getMethod(), request.getRequestURI(), - exception.getMessage()); + ExceptionCode.INVALID_PARAMETER.getClientExceptionCode().name(), + errorMessage); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(response); } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/like/controller/ChecklistLikeController.java b/backend/bang-ggood/src/main/java/com/bang_ggood/like/controller/ChecklistLikeController.java index c56bb9a62..221f9b29e 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/like/controller/ChecklistLikeController.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/like/controller/ChecklistLikeController.java @@ -24,7 +24,6 @@ public ResponseEntity createChecklistLike(@AuthRequiredPrincipal User user return ResponseEntity.noContent().build(); } - @DeleteMapping("/checklists/{id}/like") public ResponseEntity deleteChecklistLikeByChecklistId(@AuthRequiredPrincipal User user, @PathVariable("id") long id) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/like/domain/ChecklistLike.java b/backend/bang-ggood/src/main/java/com/bang_ggood/like/domain/ChecklistLike.java index aaf3f7bf5..ff4af65b3 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/like/domain/ChecklistLike.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/like/domain/ChecklistLike.java @@ -8,8 +8,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class ChecklistLike extends BaseEntity { @@ -24,17 +30,6 @@ public ChecklistLike(Checklist checklist) { this.checklist = checklist; } - protected ChecklistLike() { - } - - public Long getId() { - return id; - } - - public Checklist getChecklist() { - return checklist; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/like/repository/ChecklistLikeRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/like/repository/ChecklistLikeRepository.java index d737ad863..2dc4f65e6 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/like/repository/ChecklistLikeRepository.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/like/repository/ChecklistLikeRepository.java @@ -11,10 +11,5 @@ public interface ChecklistLikeRepository extends JpaRepository new BangggoodException(ExceptionCode.LIKE_NOT_EXISTS)); - } - Optional findByChecklistId(Long checklistId); } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeManageService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeManageService.java index f6659813b..9d9419fb5 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeManageService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeManageService.java @@ -3,20 +3,17 @@ import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.service.ChecklistService; import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@RequiredArgsConstructor @Service public class ChecklistLikeManageService { private final ChecklistService checklistService; private final ChecklistLikeService checklistLikeService; - public ChecklistLikeManageService(ChecklistService checklistService, ChecklistLikeService checklistLikeService) { - this.checklistService = checklistService; - this.checklistLikeService = checklistLikeService; - } - @Transactional public void createLike(User user, Long checklistId) { Checklist checklist = checklistService.readChecklist(user, checklistId); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeService.java index 6c9cf2f1a..464af3202 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/like/service/ChecklistLikeService.java @@ -6,38 +6,38 @@ import com.bang_ggood.like.domain.ChecklistLike; import com.bang_ggood.like.repository.ChecklistLikeRepository; import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; +@RequiredArgsConstructor @Service public class ChecklistLikeService { private final ChecklistLikeRepository checklistLikeRepository; - public ChecklistLikeService(ChecklistLikeRepository checklistLikeRepository) { - this.checklistLikeRepository = checklistLikeRepository; - } - @Transactional public void createLike(User user, Checklist checklist) { validateChecklistOwnership(user, checklist); - validateChecklistAlreadyLiked(checklist); + + if (isChecklistAlreadyLiked(checklist)) { + return; + } checklistLikeRepository.save(new ChecklistLike(checklist)); } + private boolean isChecklistAlreadyLiked(Checklist checklist) { + return checklistLikeRepository.existsByChecklist(checklist); + } + private void validateChecklistOwnership(User user, Checklist checklist) { if (!checklist.isOwnedBy(user)) { throw new BangggoodException(ExceptionCode.CHECKLIST_NOT_OWNED_BY_USER); } } - private void validateChecklistAlreadyLiked(Checklist checklist) { - if (checklistLikeRepository.existsByChecklist(checklist)) { - throw new BangggoodException(ExceptionCode.LIKE_ALREADY_EXISTS); - } - } - @Transactional public boolean isLikedChecklist(Checklist checklist) { return checklistLikeRepository.existsByChecklist(checklist); @@ -46,8 +46,12 @@ public boolean isLikedChecklist(Checklist checklist) { @Transactional public void deleteLike(User user, Checklist checklist) { validateChecklistOwnership(user, checklist); - ChecklistLike checklistLike = checklistLikeRepository.getByChecklistId(checklist.getId()); + Optional checklistLike = checklistLikeRepository.findByChecklistId(checklist.getId()); + + if (checklistLike.isEmpty()) { + return; + } - checklistLikeRepository.deleteById(checklistLike.getId()); + checklistLikeRepository.deleteById(checklistLike.get().getId()); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/domain/ChecklistMaintenance.java b/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/domain/ChecklistMaintenance.java index 3be053ffc..b7ee32f0c 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/domain/ChecklistMaintenance.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/domain/ChecklistMaintenance.java @@ -10,8 +10,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class ChecklistMaintenance extends BaseEntity { @@ -30,25 +36,10 @@ public ChecklistMaintenance(Checklist checklist, MaintenanceItem maintenanceItem this.maintenanceItem = maintenanceItem; } - protected ChecklistMaintenance() { - } - - public Long getId() { - return id; - } - - public Checklist getChecklist() { - return checklist; - } - public Integer getMaintenanceItemId() { return maintenanceItem.getId(); } - public String getMaintenanceItemName() { - return maintenanceItem.getName(); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/service/ChecklistMaintenanceService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/service/ChecklistMaintenanceService.java index 427cad630..e13f51509 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/service/ChecklistMaintenanceService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/maintenance/service/ChecklistMaintenanceService.java @@ -5,21 +5,19 @@ import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.maintenance.domain.ChecklistMaintenance; import com.bang_ggood.maintenance.repository.ChecklistMaintenanceRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashSet; import java.util.List; import java.util.Set; +@RequiredArgsConstructor @Service public class ChecklistMaintenanceService { private final ChecklistMaintenanceRepository checklistMaintenanceRepository; - public ChecklistMaintenanceService(ChecklistMaintenanceRepository checklistMaintenanceRepository) { - this.checklistMaintenanceRepository = checklistMaintenanceRepository; - } - @Transactional public void createMaintenances(List maintenances) { validateMaintenancesDuplicate(maintenances); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/option/domain/ChecklistOption.java b/backend/bang-ggood/src/main/java/com/bang_ggood/option/domain/ChecklistOption.java index 035296766..e66d9d162 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/option/domain/ChecklistOption.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/option/domain/ChecklistOption.java @@ -9,9 +9,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class ChecklistOption extends BaseEntity { @@ -30,21 +35,6 @@ public ChecklistOption(Checklist checklist, Integer optionId) { this.optionId = optionId; } - protected ChecklistOption() { - } - - public Long getId() { - return id; - } - - public Checklist getChecklist() { - return checklist; - } - - public Integer getOptionId() { - return optionId; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/option/service/ChecklistOptionService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/option/service/ChecklistOptionService.java index 6f9411509..cc069c025 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/option/service/ChecklistOptionService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/option/service/ChecklistOptionService.java @@ -5,21 +5,19 @@ import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.option.domain.ChecklistOption; import com.bang_ggood.option.repository.ChecklistOptionRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashSet; import java.util.List; import java.util.Set; +@RequiredArgsConstructor @Service public class ChecklistOptionService { private final ChecklistOptionRepository checklistOptionRepository; - public ChecklistOptionService(ChecklistOptionRepository checklistOptionRepository) { - this.checklistOptionRepository = checklistOptionRepository; - } - @Transactional public void createOptions(List options) { validateOptionDuplicate(options); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CategoryEntity.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CategoryEntity.java new file mode 100644 index 000000000..9c1dc138c --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CategoryEntity.java @@ -0,0 +1,49 @@ +package com.bang_ggood.question.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) +@Table(name = "category") //TODO 변경필요 +@Entity +public class CategoryEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private String name; + + public CategoryEntity(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CategoryEntity that = (CategoryEntity) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/ChecklistQuestion.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/ChecklistQuestion.java index 898c49b49..24232a1ce 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/ChecklistQuestion.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/ChecklistQuestion.java @@ -9,9 +9,16 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class ChecklistQuestion extends BaseEntity { @@ -25,44 +32,38 @@ public class ChecklistQuestion extends BaseEntity { @Enumerated(EnumType.STRING) private Question question; + @JoinColumn(name = "question_id") + @ManyToOne(fetch = FetchType.LAZY) + private QuestionEntity questionEntity; + @Enumerated(EnumType.STRING) private Answer answer; - public ChecklistQuestion(Checklist checklist, Question question, Answer answer) { + public ChecklistQuestion(Checklist checklist, Question question, QuestionEntity questionEntity, Answer answer) { this.checklist = checklist; this.question = question; this.answer = answer; - } - - protected ChecklistQuestion() { + this.questionEntity = questionEntity; } public void change(ChecklistQuestion checklistQuestion) { this.answer = checklistQuestion.answer; } - public boolean isDifferentQuestionId(ChecklistQuestion checklistQuestion) { - return this.question != checklistQuestion.question; - } - - public Long getId() { - return id; - } - - public Checklist getChecklist() { - return checklist; + public boolean isDifferentQuestionId(ChecklistQuestion checklistQuestion) { // TODO 리팩토링 + return !getQuestionId().equals(checklistQuestion.getQuestionId()); } - public Question getQuestion() { - return question; + public Long getChecklistId() { + return checklist.getId(); } public Integer getQuestionId() { - return question.getId(); + return questionEntity.getId(); } - public Answer getAnswer() { - return answer; + public boolean isCategory(CategoryEntity category) { + return questionEntity.getCategory().equals(category); } @Override @@ -87,7 +88,7 @@ public String toString() { return "ChecklistQuestion{" + "id=" + id + ", checklist=" + checklist + - ", question=" + question + + ", questionEntity=" + questionEntity + ", answer=" + answer + '}'; } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CustomChecklistQuestion.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CustomChecklistQuestion.java index d08c4690f..cb91cda4a 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CustomChecklistQuestion.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/CustomChecklistQuestion.java @@ -9,8 +9,15 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class CustomChecklistQuestion extends BaseEntity { @@ -24,23 +31,21 @@ public class CustomChecklistQuestion extends BaseEntity { @Enumerated(EnumType.STRING) private Question question; - public CustomChecklistQuestion(User user, Question question) { + @JoinColumn(name = "question_id") + @ManyToOne(fetch = FetchType.LAZY) + private QuestionEntity questionEntity; + + public CustomChecklistQuestion(User user, Question question, QuestionEntity questionEntity) { this.user = user; this.question = question; + this.questionEntity = questionEntity; } - protected CustomChecklistQuestion() { - } - - public Long getId() { - return id; - } - - public User getUser() { - return user; + public Integer getQuestionId() { + return questionEntity.getId(); } - public Question getQuestion() { - return question; + public boolean isSameCategory(CategoryEntity category) { + return this.questionEntity.getCategory().equals(category); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Highlight.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Highlight.java new file mode 100644 index 000000000..95b70848a --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Highlight.java @@ -0,0 +1,53 @@ +package com.bang_ggood.question.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) +@Entity +public class Highlight { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + private QuestionEntity question; + + @Column(nullable = false) + private String name; + + public Highlight(QuestionEntity question, String name) { + this.question = question; + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Highlight highlight = (Highlight) o; + return Objects.equals(id, highlight.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Question.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Question.java index 6f47c97f4..b5b0e9730 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Question.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/Question.java @@ -3,9 +3,7 @@ import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; public enum Question { @@ -51,11 +49,6 @@ public enum Question { OUTSIDE_5(31, Category.OUTSIDE, "옆 건물에서 보이는 구조인지 확인했나요?", null, List.of("보이는 구조"), false), OUTSIDE_6(32, Category.OUTSIDE, "주차할 수 있는 시설이 있나요?", null, List.of("주차할 수 있는 시설"), false); - static { - validateQuestionIdDuplication(); - validateQuestionHighlightsMisMatch(); - } - private final int id; private final Category category; private final String title; @@ -72,25 +65,6 @@ public enum Question { this.isDefault = isDefault; } - private static void validateQuestionIdDuplication() { - Set idSet = new HashSet<>(); - for (Question question : Question.values()) { - if (!idSet.add(question.getId())) { - throw new BangggoodException(ExceptionCode.QUESTION_ID_ERROR); - } - } - } - - private static void validateQuestionHighlightsMisMatch() { - for (Question question : Question.values()) { - for (String highlight : question.highlights) { - if (!question.getTitle().contains(highlight)) { - throw new BangggoodException(ExceptionCode.QUESTION_HIGHLIGHT_ERROR); - } - } - } - } - public static Question fromId(int id) { return Arrays.stream(values()) .filter(question -> question.id == id) @@ -98,18 +72,6 @@ public static Question fromId(int id) { .orElseThrow(() -> new BangggoodException(ExceptionCode.QUESTION_INVALID)); } - public static List filter(Category category, List questions) { - return questions.stream() - .filter(question -> question.getQuestion().isCategory(category) && question.getAnswer() != null) - .toList(); - } - - public static List findQuestionsByCategory(Category category) { - return Arrays.stream(values()) - .filter(question -> question.getCategory().equals(category)) - .toList(); - } - public static List findDefaultQuestions() { return Arrays.stream(values()) .filter(question -> question.isDefault) @@ -121,15 +83,6 @@ public static boolean contains(int id) { .anyMatch(question -> question.getId() == id); } - public boolean isSelected(List questions) { - return questions.stream() - .anyMatch(question -> question.getQuestion().id == this.id); - } - - private boolean isCategory(Category category) { - return this.category == category; - } - public int getId() { return id; } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/QuestionEntity.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/QuestionEntity.java new file mode 100644 index 000000000..9166ab9d0 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/domain/QuestionEntity.java @@ -0,0 +1,67 @@ +package com.bang_ggood.question.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Objects; + +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) +@Table(name = "question") //TODO 변경필요 +@Entity +public class QuestionEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + private CategoryEntity category; + + @Column(nullable = false) + private String title; + + private String subtitle; + + private boolean isDefault; + + public QuestionEntity(CategoryEntity category, String title, String subtitle, boolean isDefault) { + this.category = category; + this.title = title; + this.subtitle = subtitle; + this.isDefault = isDefault; + } + + public boolean isSelected(List questions) { + return questions.stream() + .anyMatch(question -> question.getQuestionId() == this.id); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuestionEntity that = (QuestionEntity) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryCustomChecklistQuestionResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryCustomChecklistQuestionResponse.java index 8952fcc5f..b4a7e406e 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryCustomChecklistQuestionResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryCustomChecklistQuestionResponse.java @@ -1,12 +1,12 @@ package com.bang_ggood.question.dto.response; -import com.bang_ggood.question.domain.Category; +import com.bang_ggood.question.domain.CategoryEntity; import java.util.List; public record CategoryCustomChecklistQuestionResponse(Integer categoryId, String categoryName, List questions) { - public static CategoryCustomChecklistQuestionResponse of(Category category, + public static CategoryCustomChecklistQuestionResponse of(CategoryEntity category, List questions) { return new CategoryCustomChecklistQuestionResponse(category.getId(), category.getName(), questions); } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryQuestionsResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryQuestionsResponse.java index 73337c660..9cb38716c 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryQuestionsResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CategoryQuestionsResponse.java @@ -1,11 +1,11 @@ package com.bang_ggood.question.dto.response; -import com.bang_ggood.question.domain.Category; +import com.bang_ggood.question.domain.CategoryEntity; import java.util.List; public record CategoryQuestionsResponse(Integer categoryId, String categoryName, List questions) { - public static CategoryQuestionsResponse of(Category category, List questions) { + public static CategoryQuestionsResponse of(CategoryEntity category, List questions) { return new CategoryQuestionsResponse(category.getId(), category.getName(), questions); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CustomChecklistQuestionResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CustomChecklistQuestionResponse.java index a5b4e183d..46cff872f 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CustomChecklistQuestionResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/CustomChecklistQuestionResponse.java @@ -1,13 +1,15 @@ package com.bang_ggood.question.dto.response; -import com.bang_ggood.question.domain.Question; +import com.bang_ggood.question.domain.Highlight; +import com.bang_ggood.question.domain.QuestionEntity; +import java.util.List; public class CustomChecklistQuestionResponse extends QuestionResponse { private final boolean isSelected; - public CustomChecklistQuestionResponse(Question question, boolean isSelected) { - super(question); + public CustomChecklistQuestionResponse(QuestionEntity question, List highlights, boolean isSelected) { + super(question, highlights); this.isSelected = isSelected; } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/QuestionResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/QuestionResponse.java index f236f7cc7..a99c6084c 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/QuestionResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/QuestionResponse.java @@ -1,6 +1,7 @@ package com.bang_ggood.question.dto.response; -import com.bang_ggood.question.domain.Question; +import com.bang_ggood.question.domain.Highlight; +import com.bang_ggood.question.domain.QuestionEntity; import java.util.List; public class QuestionResponse { @@ -10,11 +11,13 @@ public class QuestionResponse { private final String subtitle; private final List highlights; - public QuestionResponse(Question question) { + public QuestionResponse(QuestionEntity question, List highlights) { this.questionId = question.getId(); this.title = question.getTitle(); this.subtitle = question.getSubtitle(); - this.highlights = question.getHighlights(); + this.highlights = highlights.stream() + .map(Highlight::getName) + .toList(); } public Integer getQuestionId() { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedCategoryQuestionsResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedCategoryQuestionsResponse.java index 3cb7f7568..d89f41d5c 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedCategoryQuestionsResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedCategoryQuestionsResponse.java @@ -1,12 +1,12 @@ package com.bang_ggood.question.dto.response; -import com.bang_ggood.question.domain.Category; +import com.bang_ggood.question.domain.CategoryEntity; import java.util.List; public record SelectedCategoryQuestionsResponse(Integer categoryId, String categoryName, List questions) { - public static SelectedCategoryQuestionsResponse of(Category category, List questions) { + public static SelectedCategoryQuestionsResponse of(CategoryEntity category, List questions) { return new SelectedCategoryQuestionsResponse( category.getId(), category.getName(), diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedQuestionResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedQuestionResponse.java index 64f05eb92..fa3287f84 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedQuestionResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/dto/response/SelectedQuestionResponse.java @@ -1,13 +1,15 @@ package com.bang_ggood.question.dto.response; import com.bang_ggood.question.domain.ChecklistQuestion; +import com.bang_ggood.question.domain.Highlight; +import java.util.List; public class SelectedQuestionResponse extends QuestionResponse { private final String answer; - public SelectedQuestionResponse(ChecklistQuestion checklistQuestion) { - super(checklistQuestion.getQuestion()); + public SelectedQuestionResponse(ChecklistQuestion checklistQuestion, List highlights) { + super(checklistQuestion.getQuestionEntity(), highlights); this.answer = checklistQuestion.getAnswer().name(); } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/CategoryRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/CategoryRepository.java new file mode 100644 index 000000000..56a013eb1 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/CategoryRepository.java @@ -0,0 +1,17 @@ +package com.bang_ggood.question.repository; + +import com.bang_ggood.question.domain.CategoryEntity; +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 CategoryRepository extends JpaRepository { + + @Query(value = "SELECT distinct c.* FROM category c " + + "JOIN question q ON q.category_id = c.id " + + "JOIN custom_checklist_question ccq ON ccq.question_id = q.id " + + "WHERE ccq.user_id = :userId AND ccq.deleted = false ", + nativeQuery = true) + List findAllCustomQuestionCategoriesByUserId(@Param("userId") Long userId); +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/ChecklistQuestionRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/ChecklistQuestionRepository.java index ff505d864..043fb13a2 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/ChecklistQuestionRepository.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/ChecklistQuestionRepository.java @@ -21,4 +21,9 @@ public interface ChecklistQuestionRepository extends JpaRepository { + + List findAllByQuestionId(Integer id); +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/QuestionRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/QuestionRepository.java new file mode 100644 index 000000000..be2f4fb8c --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/repository/QuestionRepository.java @@ -0,0 +1,16 @@ +package com.bang_ggood.question.repository; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import com.bang_ggood.question.domain.QuestionEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface QuestionRepository extends JpaRepository { + + default QuestionEntity getById(Integer id) { + return findById(id).orElseThrow(() -> new BangggoodException(ExceptionCode.QUESTION_INVALID)); + } + + List findAllByCategoryId(Integer id); +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/ChecklistQuestionService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/ChecklistQuestionService.java index 22deb5657..afc25d5af 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/ChecklistQuestionService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/ChecklistQuestionService.java @@ -3,29 +3,28 @@ import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; +import com.bang_ggood.question.domain.CategoryEntity; import com.bang_ggood.question.domain.ChecklistQuestion; import com.bang_ggood.question.domain.CustomChecklistQuestion; import com.bang_ggood.question.domain.Question; import com.bang_ggood.question.repository.ChecklistQuestionRepository; import com.bang_ggood.question.repository.CustomChecklistQuestionRepository; import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +@RequiredArgsConstructor @Service public class ChecklistQuestionService { private final ChecklistQuestionRepository checklistQuestionRepository; private final CustomChecklistQuestionRepository customChecklistQuestionRepository; - - public ChecklistQuestionService(ChecklistQuestionRepository checklistQuestionRepository, - CustomChecklistQuestionRepository customChecklistQuestionRepository) { - this.checklistQuestionRepository = checklistQuestionRepository; - this.customChecklistQuestionRepository = customChecklistQuestionRepository; - } + private final QuestionService questionService; // TODO 리팩터링 @Transactional public void createDefaultCustomQuestions(List customChecklistQuestions) { @@ -60,7 +59,7 @@ public void updateCustomChecklist(User user, List questions) { customChecklistQuestionRepository.deleteAllByUser(user); List customChecklistQuestions = questions.stream() - .map(question -> new CustomChecklistQuestion(user, question)) + .map(question -> new CustomChecklistQuestion(user, question, questionService.readQuestion(question.getId()))) .toList(); customChecklistQuestionRepository.saveAll(customChecklistQuestions); } @@ -79,7 +78,29 @@ private void validateCustomChecklistQuestionsDuplication(List question @Transactional(readOnly = true) public List readChecklistQuestions(Checklist checklist) { - return checklistQuestionRepository.findAllByChecklistId(checklist.getId()); + List checklistQuestions = checklistQuestionRepository.findAllByChecklistId(checklist.getId()); + + List result = new ArrayList<>(); + for (ChecklistQuestion checklistQuestion : checklistQuestions) { + if (checklistQuestion.getQuestionEntity() == null) { + ChecklistQuestion checklistQuestion1 = checklistQuestionRepository.updateChecklistQuestionId( + checklistQuestion.getQuestionId(), checklistQuestion.getId()); + + result.add(checklistQuestion1); + continue; + } + + result.add(checklistQuestion); + } + + return result; + } + + @Transactional(readOnly = true) + public List categorizeChecklistQuestions(CategoryEntity category, List questions) { + return questions.stream() + .filter(question -> question.isCategory(category) && question.getAnswer() != null) + .toList(); } @Transactional diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionManageService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionManageService.java index 5b308e43e..2d7cac814 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionManageService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionManageService.java @@ -1,8 +1,9 @@ package com.bang_ggood.question.service; -import com.bang_ggood.question.domain.Category; +import com.bang_ggood.question.domain.CategoryEntity; import com.bang_ggood.question.domain.CustomChecklistQuestion; import com.bang_ggood.question.domain.Question; +import com.bang_ggood.question.domain.QuestionEntity; import com.bang_ggood.question.dto.request.CustomChecklistUpdateRequest; import com.bang_ggood.question.dto.response.CategoryCustomChecklistQuestionResponse; import com.bang_ggood.question.dto.response.CategoryCustomChecklistQuestionsResponse; @@ -11,28 +12,24 @@ import com.bang_ggood.question.dto.response.CustomChecklistQuestionsResponse; import com.bang_ggood.question.dto.response.QuestionResponse; import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +@RequiredArgsConstructor @Service public class QuestionManageService { private final ChecklistQuestionService checklistQuestionService; - - public QuestionManageService(ChecklistQuestionService checklistQuestionService) { - this.checklistQuestionService = checklistQuestionService; - } + private final QuestionService questionService; @Transactional public void createDefaultCustomChecklistQuestions(User user) { List customChecklistQuestions = Question.findDefaultQuestions() .stream() - .map(question -> new CustomChecklistQuestion(user, question)) + .map(question -> new CustomChecklistQuestion(user, question, questionService.readQuestion(question.getId()))) // TODO : 변경필요 .toList(); checklistQuestionService.createDefaultCustomQuestions(customChecklistQuestions); @@ -42,24 +39,28 @@ public void createDefaultCustomChecklistQuestions(User user) { public CustomChecklistQuestionsResponse readCustomChecklistQuestions(User user) { List customChecklistQuestions = checklistQuestionService.readCustomChecklistQuestions( user); - List categoryQuestionsResponses = categorizeCustomChecklistQuestions( - customChecklistQuestions); + List categoryQuestionsResponses = categorizeCustomChecklistQuestions(user, customChecklistQuestions).stream() + .filter(categoryQuestionsResponse -> !categoryQuestionsResponse.questions().isEmpty()) + .toList(); + return new CustomChecklistQuestionsResponse(categoryQuestionsResponses); } private List categorizeCustomChecklistQuestions( + User user, List customChecklistQuestions) { - Map> categoryQuestions = customChecklistQuestions.stream() - .map(CustomChecklistQuestion::getQuestion) - .collect(Collectors.groupingBy(Question::getCategory, LinkedHashMap::new, Collectors.toList())); - - return categoryQuestions.entrySet().stream() - .map(categoryQuestionEntry -> CategoryQuestionsResponse.of( - categoryQuestionEntry.getKey(), - categoryQuestionEntry.getValue().stream() - .map(QuestionResponse::new) - .toList())) - .toList(); + List categoryQuestionsResponses = new ArrayList<>(); + + for (CategoryEntity category : questionService.findAllCustomQuestionCategories(user)) { + List questionResponses = customChecklistQuestions.stream() + .filter(customChecklistQuestion -> customChecklistQuestion.isSameCategory(category)) // TODO 리팩토링 + .map(customChecklistQuestion -> new QuestionResponse(customChecklistQuestion.getQuestionEntity(), questionService.readHighlights(customChecklistQuestion.getQuestionId()))) + .toList(); + + categoryQuestionsResponses.add(CategoryQuestionsResponse.of(category, questionResponses)); + } + + return categoryQuestionsResponses; } @Transactional(readOnly = true) @@ -73,10 +74,12 @@ private CategoryCustomChecklistQuestionsResponse categorizeAllQuestionsWithSelec List customChecklistQuestions) { List response = new ArrayList<>(); - for (Category category : Category.values()) { - List categoryQuestions = Question.findQuestionsByCategory(category); + for (CategoryEntity category : questionService.findAllCategories()) { + List categoryQuestions = questionService.readQuestionsByCategory(category); List questions = categoryQuestions.stream() - .map(question -> new CustomChecklistQuestionResponse(question, + .map(question -> new CustomChecklistQuestionResponse( + question, + questionService.readHighlights(question.getId()), question.isSelected(customChecklistQuestions))) .toList(); response.add(CategoryCustomChecklistQuestionResponse.of(category, questions)); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionService.java new file mode 100644 index 000000000..19e2f073e --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/question/service/QuestionService.java @@ -0,0 +1,47 @@ +package com.bang_ggood.question.service; + +import com.bang_ggood.question.domain.CategoryEntity; +import com.bang_ggood.question.domain.Highlight; +import com.bang_ggood.question.domain.QuestionEntity; +import com.bang_ggood.question.repository.CategoryRepository; +import com.bang_ggood.question.repository.HighlightRepository; +import com.bang_ggood.question.repository.QuestionRepository; +import com.bang_ggood.user.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; + +@RequiredArgsConstructor +@Service +public class QuestionService { + + private final CategoryRepository categoryRepository; + private final QuestionRepository questionRepository; + private final HighlightRepository highlightRepository; + + @Transactional(readOnly = true) + public List findAllCategories() { + return categoryRepository.findAll(); + } + + @Transactional(readOnly = true) + public List findAllCustomQuestionCategories(User user) { + return categoryRepository.findAllCustomQuestionCategoriesByUserId(user.getId()); + } + + @Transactional(readOnly = true) + public QuestionEntity readQuestion(Integer questionId) { + return questionRepository.getById(questionId); + } + + @Transactional(readOnly = true) + public List readHighlights(Integer questionId) { + return highlightRepository.findAllByQuestionId(questionId); + } + + @Transactional(readOnly = true) + public List readQuestionsByCategory(CategoryEntity category) { + return questionRepository.findAllByCategoryId(category.getId()); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/room/domain/Room.java b/backend/bang-ggood/src/main/java/com/bang_ggood/room/domain/Room.java index ddac9a8f1..3ab48d5e1 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/room/domain/Room.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/room/domain/Room.java @@ -9,8 +9,14 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class Room extends BaseEntity { @@ -38,9 +44,6 @@ public class Room extends BaseEntity { private Double size; - protected Room() { - } - public Room(String name, String address, String buildingName, String station, Integer walkingTime, FloorLevel floorLevel, Integer floor, Structure structure, Double size) { this.name = name; @@ -74,46 +77,6 @@ private void validateFloorAndLevel() { } } - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public String getAddress() { - return address; - } - - public String getBuildingName() { - return buildingName; - } - - public String getStation() { - return station; - } - - public Integer getWalkingTime() { - return walkingTime; - } - - public FloorLevel getFloorLevel() { - return floorLevel; - } - - public Integer getFloor() { - return floor; - } - - public Structure getStructure() { - return structure; - } - - public Double getSize() { - return size; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponse.java index 475010d99..1ad7c1023 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponse.java @@ -9,7 +9,8 @@ public record SelectedRoomResponse(String roomName, Integer deposit, Integer ren String realEstate, Double size, String floorLevel, String structure, Integer occupancyMonth, String occupancyPeriod, String memo, String summary, - List includedMaintenances, Integer maintenanceFee, LocalDateTime createdAt) { + List includedMaintenances, Integer maintenanceFee, + LocalDateTime createdAt) { public static SelectedRoomResponse of(Checklist checklist, List includedMaintenances) { return new SelectedRoomResponse(checklist.getRoomName(), checklist.getDeposit(), checklist.getRent(), @@ -18,6 +19,7 @@ public static SelectedRoomResponse of(Checklist checklist, List include checklist.getRoomStation(), checklist.getRoomWalkingTime(), checklist.getRealEstate(), checklist.getRoomSize(), checklist.getRoomFloorLevel().getName(), checklist.getRoomStructure().getName(), checklist.getOccupancyMonth(), checklist.getOccupancyPeriod(), - checklist.getMemo(), checklist.getSummary(), includedMaintenances, checklist.getMaintenanceFee(), checklist.getCreatedAt()); + checklist.getMemo(), checklist.getSummary(), includedMaintenances, checklist.getMaintenanceFee(), + checklist.getCreatedAt()); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponseV1.java b/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponseV1.java new file mode 100644 index 000000000..73ba23eea --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/room/dto/response/SelectedRoomResponseV1.java @@ -0,0 +1,24 @@ +package com.bang_ggood.room.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +public record SelectedRoomResponseV1(String roomName, Integer deposit, Integer rent, Integer contractTerm, + Integer floor, + String address, String buildingName, String realEstate, + Double size, String floorLevel, String structure, + Integer occupancyMonth, String occupancyPeriod, String memo, String summary, + List includedMaintenances, Integer maintenanceFee, + LocalDateTime createdAt) { + + public static SelectedRoomResponseV1 from(SelectedRoomResponse response) { + return new SelectedRoomResponseV1( + response.roomName(), response.deposit(), response.rent(), + response.contractTerm(), response.floor(), response.address(), + response.buildingName(), response.realEstate(), + response.size(), response.floorLevel(), response.structure(), + response.occupancyMonth(), response.occupancyPeriod(), response.memo(), response.summary(), + response.includedMaintenances(), response.maintenanceFee(), response.createdAt() + ); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/room/service/RoomService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/room/service/RoomService.java index c800f4aa2..46e3634e9 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/room/service/RoomService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/room/service/RoomService.java @@ -2,18 +2,16 @@ import com.bang_ggood.room.domain.Room; import com.bang_ggood.room.repository.RoomRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@RequiredArgsConstructor @Service public class RoomService { private final RoomRepository roomRepository; - public RoomService(RoomRepository roomRepository) { - this.roomRepository = roomRepository; - } - @Transactional public Room createRoom(Room room) { return roomRepository.save(room); diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/SubwayReader.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/SubwayReader.java index b17400424..78d0186bd 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/station/SubwayReader.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/SubwayReader.java @@ -22,7 +22,8 @@ public class SubwayReader { public static List readSubwayStationData() { List stations = new ArrayList<>(); try (CSVReader csvReader = new CSVReaderBuilder( - new InputStreamReader(getSubwayStationResource().getInputStream(), Charset.forName("EUC-KR"))).build()) { + new InputStreamReader(getSubwayStationResource().getInputStream(), + Charset.forName("EUC-KR"))).build()) { String[] line = csvReader.readNext(); // drop first row while ((line = csvReader.readNext()) != null) { SubwayStation station = new SubwayStation( diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/controller/SubwayStationController.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/controller/SubwayStationController.java index 9e0178afc..6549e461a 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/station/controller/SubwayStationController.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/controller/SubwayStationController.java @@ -1,12 +1,11 @@ package com.bang_ggood.station.controller; -import com.bang_ggood.station.dto.SubwayStationResponse; +import com.bang_ggood.station.dto.response.SubwayStationResponses; import com.bang_ggood.station.service.SubwayStationService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.List; @RestController public class SubwayStationController { @@ -18,10 +17,10 @@ public SubwayStationController(SubwayStationService subwayStationService) { } @GetMapping("/stations/nearest") - public ResponseEntity> readNearestStation(@RequestParam("latitude") Double latitude, - @RequestParam("longitude") Double longitude) { + public ResponseEntity readNearestStation(@RequestParam("latitude") Double latitude, + @RequestParam("longitude") Double longitude) { - List response = subwayStationService.readNearestStation(latitude, longitude); + SubwayStationResponses response = subwayStationService.readNearestStation(latitude, longitude); return ResponseEntity.ok(response); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/ChecklistStation.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/ChecklistStation.java new file mode 100644 index 000000000..87029184f --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/ChecklistStation.java @@ -0,0 +1,58 @@ +package com.bang_ggood.station.domain; + +import com.bang_ggood.BaseEntity; +import com.bang_ggood.checklist.domain.Checklist; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.util.Objects; + +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) +@Entity +public class ChecklistStation extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Checklist checklist; + + private String stationName; + + private String stationLine; + + private int walkingTime; + + public ChecklistStation(Checklist checklist, String stationName, String stationLine, int walkingTime) { + this.checklist = checklist; + this.stationName = stationName; + this.stationLine = stationLine; + this.walkingTime = walkingTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ChecklistStation that = (ChecklistStation) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/SubwayStation.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/SubwayStation.java index 94a385d35..9559ea5ec 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/SubwayStation.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/domain/SubwayStation.java @@ -1,5 +1,8 @@ package com.bang_ggood.station.domain; +import lombok.Getter; + +@Getter public class SubwayStation { private static final int METER_PER_DEGREE = 111_320; @@ -28,26 +31,6 @@ public int calculateWalkingTime(double latitude, double longitude) { return (int) (Math.round(distance) / AVERAGE_WALKING_SPEED); } - public Integer getId() { - return id; - } - - public String getName() { - return name; - } - - public String getLine() { - return line; - } - - public double getLatitude() { - return latitude; - } - - public double getLongitude() { - return longitude; - } - @Override public String toString() { return "Station{" + diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/request/ChecklistStationRequest.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/request/ChecklistStationRequest.java new file mode 100644 index 000000000..4b4db9c47 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/request/ChecklistStationRequest.java @@ -0,0 +1,8 @@ +package com.bang_ggood.station.dto.request; + +public record ChecklistStationRequest(double latitude, double longitude) { + + public static ChecklistStationRequest of(double latitude, double longitude) { + return new ChecklistStationRequest(latitude, longitude); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/SubwayStationResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/response/SubwayStationResponse.java similarity index 62% rename from backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/SubwayStationResponse.java rename to backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/response/SubwayStationResponse.java index 165c3a6e8..8227cd739 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/SubwayStationResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/response/SubwayStationResponse.java @@ -1,27 +1,34 @@ -package com.bang_ggood.station.dto; +package com.bang_ggood.station.dto.response; import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; +import com.bang_ggood.station.domain.ChecklistStation; import com.bang_ggood.station.domain.SubwayStation; +import lombok.AllArgsConstructor; +import lombok.Getter; import java.util.ArrayList; import java.util.List; +@AllArgsConstructor +@Getter public class SubwayStationResponse { private final String stationName; private final List stationLine; private Integer walkingTime; - public SubwayStationResponse(String stationName, List stationLine, Integer walkingTime) { - this.stationName = stationName; - this.stationLine = stationLine; - this.walkingTime = walkingTime; - } - public static SubwayStationResponse of(SubwayStation station, double latitude, double longitude) { List stationLine = new ArrayList<>(); stationLine.add(station.getLine()); - return new SubwayStationResponse(station.getName(), stationLine, station.calculateWalkingTime(latitude, longitude)); + return new SubwayStationResponse(station.getName(), stationLine, + station.calculateWalkingTime(latitude, longitude)); + } + + public static SubwayStationResponse from(ChecklistStation checklistStation) { + List stationLine = new ArrayList<>(); + stationLine.add(checklistStation.getStationLine()); + return new SubwayStationResponse(checklistStation.getStationName(), stationLine, + checklistStation.getWalkingTime()); } public SubwayStationResponse merge(SubwayStationResponse response) { @@ -33,16 +40,4 @@ public SubwayStationResponse merge(SubwayStationResponse response) { walkingTime = Math.min(walkingTime, response.walkingTime); return this; } - - public String getStationName() { - return stationName; - } - - public List getStationLine() { - return stationLine; - } - - public Integer getWalkingTime() { - return walkingTime; - } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/response/SubwayStationResponses.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/response/SubwayStationResponses.java new file mode 100644 index 000000000..7a69c0e02 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/dto/response/SubwayStationResponses.java @@ -0,0 +1,37 @@ +package com.bang_ggood.station.dto.response; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class SubwayStationResponses { + + private static final int REQUESTED_STATION_NUMBER = 2; + + private final List stations; + + public static SubwayStationResponses from(List stations) { + return new SubwayStationResponses(mergeTransferStations(stations)); + } + + private static List mergeTransferStations(List stations) { + return stations.stream() + .collect(Collectors.groupingBy( + SubwayStationResponse::getStationName, + Collectors.reducing(SubwayStationResponse::merge) + )) + .values() + .stream() + .map(optional -> optional.orElseThrow(() -> new BangggoodException(ExceptionCode.STATION_NOT_FOUND))) + .sorted(Comparator.comparing(SubwayStationResponse::getWalkingTime)) + .limit(REQUESTED_STATION_NUMBER) + .toList(); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/repository/ChecklistStationRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/repository/ChecklistStationRepository.java new file mode 100644 index 000000000..704ed8ced --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/repository/ChecklistStationRepository.java @@ -0,0 +1,16 @@ +package com.bang_ggood.station.repository; + +import com.bang_ggood.checklist.domain.Checklist; +import com.bang_ggood.station.domain.ChecklistStation; +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 ChecklistStationRepository extends JpaRepository { + + @Query("SELECT cs FROM ChecklistStation cs " + + "where cs.checklist = :checklist " + + "and cs.deleted = false") + List findByChecklist(@Param("checklist") Checklist checklist); +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/ChecklistStationService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/ChecklistStationService.java new file mode 100644 index 000000000..5ab30f0eb --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/ChecklistStationService.java @@ -0,0 +1,40 @@ +package com.bang_ggood.station.service; + +import com.bang_ggood.checklist.domain.Checklist; +import com.bang_ggood.station.domain.ChecklistStation; +import com.bang_ggood.station.dto.response.SubwayStationResponse; +import com.bang_ggood.station.repository.ChecklistStationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Service +public class ChecklistStationService { + + private final ChecklistStationRepository checklistStationRepository; + private final SubwayStationService subwayStationService; + + @Transactional + public void createChecklistStations(Checklist checklist, double latitude, double longitude) { + List responses = subwayStationService.readNearestStation(latitude, longitude) + .getStations(); + List checklistStations = new ArrayList<>(); + + for (SubwayStationResponse response : responses) { + for (String stationLine : response.getStationLine()) { + checklistStations.add(new ChecklistStation(checklist, response.getStationName(), stationLine, + response.getWalkingTime())); + } + } + + checklistStationRepository.saveAll(checklistStations); + } + + @Transactional(readOnly = true) + public List readChecklistStationsByChecklist(Checklist checklist) { + return checklistStationRepository.findByChecklist(checklist); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/SubwayStationService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/SubwayStationService.java index e80a42da2..6114a874e 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/SubwayStationService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/station/service/SubwayStationService.java @@ -1,51 +1,22 @@ package com.bang_ggood.station.service; -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.station.SubwayReader; import com.bang_ggood.station.domain.SubwayStation; -import com.bang_ggood.station.dto.SubwayStationResponse; +import com.bang_ggood.station.dto.response.SubwayStationResponse; +import com.bang_ggood.station.dto.response.SubwayStationResponses; import org.springframework.stereotype.Service; -import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; @Service public class SubwayStationService { - private static final int MAX_NESTING_STATION_NUMBER = 4; - private static final int REQUESTED_STATION_NUMBER = 2; private static final List SUBWAY_STATIONS = SubwayReader.readSubwayStationData(); - public List readNearestStation(double latitude, double longitude) { - List likelyNearStations = findLikelyNearStations(latitude, longitude); - List mergedNearStations = mergeTransferStations(likelyNearStations); - - return mergedNearStations.stream() - .sorted(Comparator.comparing(SubwayStationResponse::getWalkingTime)) - .limit(REQUESTED_STATION_NUMBER) - .toList(); - } - - private List findLikelyNearStations(double latitude, double longitude) { - return SUBWAY_STATIONS.stream() + public SubwayStationResponses readNearestStation(double latitude, double longitude) { + List stationResponses = SUBWAY_STATIONS.stream() .map(station -> SubwayStationResponse.of(station, latitude, longitude)) - .sorted(Comparator.comparing(SubwayStationResponse::getWalkingTime)) - .limit(MAX_NESTING_STATION_NUMBER * REQUESTED_STATION_NUMBER) .toList(); - } - private List mergeTransferStations(List stations) { - return stations.stream() - .collect(Collectors.groupingBy( - SubwayStationResponse::getStationName, - Collectors.reducing(SubwayStationResponse::merge) - )) - .values() - .stream() - .map(optional -> optional.orElseThrow(() -> new BangggoodException(ExceptionCode.STATION_NOT_FOUND))) - .toList(); + return SubwayStationResponses.from(stationResponses); } - - } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/Email.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/Email.java new file mode 100644 index 000000000..533d3abe6 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/Email.java @@ -0,0 +1,55 @@ +package com.bang_ggood.user.domain; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.util.Objects; +import java.util.regex.Pattern; + +import static lombok.AccessLevel.PROTECTED; + +@Embeddable +@Getter +@NoArgsConstructor(access = PROTECTED) +public class Email { + + //이메일은 영문 대소문자, 숫자, 점, 하이픈, 언더스코어, 플러스 기호를 포함할 수 있으며, + // "@" 기호 뒤에 도메인 이름이 필요하고, + // 마지막에는 최소 2글자의 영문자로 이루어진 최상위 도메인이 포함되어야 한다.. + private static final Pattern EMAIL_PATTERN = + Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); + + @Column(name = "email") + private String value; + + public Email(String value) { + validateEmailPattern(value); + this.value = value; + } + + public void validateEmailPattern(String email) { + if (!EMAIL_PATTERN.matcher(email).matches()) { + throw new BangggoodException(ExceptionCode.EMAIL_INVALID_FORMAT); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Email email = (Email) o; + return Objects.equals(value, email.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/LoginType.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/LoginType.java new file mode 100644 index 000000000..b076197b2 --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/LoginType.java @@ -0,0 +1,6 @@ +package com.bang_ggood.user.domain; + +public enum LoginType { + + LOCAL, KAKAO +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/Password.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/Password.java new file mode 100644 index 000000000..e8d21a3ad --- /dev/null +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/Password.java @@ -0,0 +1,59 @@ +package com.bang_ggood.user.domain; + +import com.bang_ggood.auth.service.PasswordEncoder; +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.util.Objects; +import java.util.regex.Pattern; + +import static lombok.AccessLevel.PROTECTED; + +@Getter +@Embeddable +@NoArgsConstructor(access = PROTECTED) +public class Password { + + //비밀번호는 최소 6자 이상이어야 하며, 영어 문자와 숫자를 각각 1개 이상 포함해야 한다. + private static final Pattern PASSWORD_PATTERN = + Pattern.compile("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}$"); + + @Column(name = "password") + private String value; + + public Password(String value) { + validatePasswordPattern(value); + this.value = PasswordEncoder.encodeWithGeneralSalt(value); + } + + public boolean isDifferent(String password) { + String targetPassword = PasswordEncoder.encodeWithSpecificSalt(password, value); + return !value.equals(targetPassword); + } + + private void validatePasswordPattern(String password) { + if (password == null || !PASSWORD_PATTERN.matcher(password).matches()) { + throw new BangggoodException(ExceptionCode.PASSWORD_INVALID_FORMAT); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Password targetPassword = (Password) o; + return Objects.equals(value, targetPassword.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/User.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/User.java index 621ec7d2a..164733012 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/User.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/domain/User.java @@ -2,6 +2,7 @@ import com.bang_ggood.BaseEntity; import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -9,9 +10,15 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Objects; +import static lombok.AccessLevel.PROTECTED; + @Table(name = "users") +@Getter +@NoArgsConstructor(access = PROTECTED) @Entity public class User extends BaseEntity { @@ -22,41 +29,43 @@ public class User extends BaseEntity { private String name; @Column(nullable = false) - private String email; + @Embedded + private Email email; + + @Embedded + private Password password; @Column(nullable = false) @Enumerated(EnumType.STRING) - private UserType type; + private UserType userType; - public User(String name, String email, UserType type) { - this.name = name; - this.email = email; - this.type = type; - } + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private LoginType loginType; - public User(Long id, String name, String email) { // TODO 테스트용 - this.id = id; + public User(String name, String email, UserType userType, LoginType loginType) { this.name = name; - this.email = email; + this.email = new Email(email); + this.userType = userType; + this.loginType = loginType; } - protected User() { - } - - public Long getId() { - return id; - } - - public String getName() { - return name; + public User(String name, String email, String password, UserType userType, LoginType loginType) { + this.name = name; + this.email = new Email(email); + this.password = new Password(password); + this.userType = userType; + this.loginType = loginType; } - public String getEmail() { - return email; + public User(Long id, String name, String email) { // TODO 테스트용 + this.id = id; + this.name = name; + this.email = new Email(email); } - public UserType getType() { - return type; + public boolean isDifferent(String targetPassword) { + return password.isDifferent(targetPassword); } @Override diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/dto/UserResponse.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/dto/UserResponse.java index 10b9e8a45..771112dff 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/user/dto/UserResponse.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/dto/UserResponse.java @@ -6,7 +6,7 @@ public record UserResponse(Long userId, String userName, String userEmail, LocalDateTime createdAt) { public static UserResponse from(User user) { return new UserResponse( - user.getId(), user.getName(), user.getEmail(), user.getCreatedAt() + user.getId(), user.getName(), user.getEmail().getValue(), user.getCreatedAt() ); } } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java index a4b31ce11..7b71728ce 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java @@ -2,6 +2,8 @@ import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; +import com.bang_ggood.user.domain.Email; +import com.bang_ggood.user.domain.LoginType; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.domain.UserType; import org.springframework.data.jpa.repository.JpaRepository; @@ -18,14 +20,18 @@ default User getUserById(Long id) { return findById(id).orElseThrow(() -> new BangggoodException(ExceptionCode.USER_NOT_FOUND)); } - @Query("SELECT u FROM User u WHERE u.type = :type and u.deleted = false ") - List findUserByType(@Param("type") UserType type); + @Query("SELECT u FROM User u WHERE u.id = :id AND u.deleted = false") + Optional findById(@Param("id") Long id); - @Query("SELECT u FROM User u WHERE u.email = :email and u.deleted = false ") - Optional findByEmail(@Param("email") String email); + @Query("SELECT u FROM User u WHERE u.userType = :userType and u.deleted = false ") + List findUserByUserType(@Param("userType") UserType userType); + + @Query("SELECT u FROM User u WHERE u.email = :email and u.loginType = :loginType and u.deleted = false") + Optional findByEmailAndLoginType(@Param("email") Email email, @Param("loginType") LoginType loginType); @Transactional @Modifying(flushAutomatically = true, clearAutomatically = true) - @Query("UPDATE User u SET u.deleted = true ") - void deleteByUser(@Param("user") User user); + @Query("UPDATE User u SET u.deleted = true WHERE u.id = :id") + void deleteById(@Param("id") Long id); + } diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/service/UserService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/service/UserService.java index cfb7ed2b9..63b03b2f9 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/user/service/UserService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/service/UserService.java @@ -3,19 +3,17 @@ import com.bang_ggood.user.domain.User; import com.bang_ggood.user.domain.UserType; import com.bang_ggood.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +@RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; - public UserService(UserRepository userRepository) { - this.userRepository = userRepository; - } - @Transactional public void createUser(User user) { userRepository.save(user); @@ -23,6 +21,6 @@ public void createUser(User user) { @Transactional(readOnly = true) public List readUser(UserType userType) { - return userRepository.findUserByType(userType); + return userRepository.findUserByUserType(userType); } } diff --git a/backend/bang-ggood/src/main/resources/.DS_Store b/backend/bang-ggood/src/main/resources/.DS_Store new file mode 100644 index 000000000..0e8448f03 Binary files /dev/null and b/backend/bang-ggood/src/main/resources/.DS_Store differ diff --git a/backend/bang-ggood/src/main/resources/data.sql b/backend/bang-ggood/src/main/resources/data.sql index 164e528e2..e49ba0d45 100644 --- a/backend/bang-ggood/src/main/resources/data.sql +++ b/backend/bang-ggood/src/main/resources/data.sql @@ -1,26 +1,110 @@ -INSERT INTO users(name, email, type, created_at, modified_at, deleted) -VALUES ('방방이', 'bang-ggood@gmail.com', 'USER', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false); +-- 비밀번호 : password1234 +INSERT INTO users(name, email, password, user_type, login_type, created_at, modified_at, deleted) +VALUES ('방방이', 'bang-ggood@gmail.com', + 'xDNYKEJqE/36U0Dt3nXRMFPNEMEgjCYM7R/A4B29baOsv4KYQ9MGgcO3HUa11sNKCFb9ZXyYBqJqxNglvBzFvg==:7yejAszEpxBb7AyZNKvAqpmMEJiKFXIa8JKwAx3n4loB2DRcAC2pfwkgo/dzKzRvBX4RbrATWaIlPYrgAhbHZQ==', + 'USER', 'LOCAL', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false); -INSERT INTO custom_checklist_question(user_id, question, created_at, modified_at, deleted) -VALUES (1, 'ROOM_CONDITION_1', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false), - (1, 'ROOM_CONDITION_2', '2024-07-22 07:56:43', '2024-07-22 07:56:43', false), - (1, 'ROOM_CONDITION_3', '2024-07-22 07:56:44', '2024-07-22 07:56:44', false), - (1, 'ROOM_CONDITION_4', '2024-07-22 07:56:45', '2024-07-22 07:56:45', false), - (1, 'ROOM_CONDITION_5', '2024-07-22 07:56:46', '2024-07-22 07:56:46', false), - (1, 'WINDOW_1', '2024-07-22 07:56:47', '2024-07-22 07:56:47', false), - (1, 'WINDOW_2', '2024-07-22 07:56:48', '2024-07-22 07:56:48', false), - (1, 'WINDOW_3', '2024-07-22 07:56:49', '2024-07-22 07:56:49', false), - (1, 'WINDOW_4', '2024-07-22 07:56:50', '2024-07-22 07:56:50', false), - (1, 'BATHROOM_1', '2024-07-22 07:56:51', '2024-07-22 07:56:51', false), - (1, 'BATHROOM_2', '2024-07-22 07:56:52', '2024-07-22 07:56:52', false), - (1, 'BATHROOM_3', '2024-07-22 07:56:53', '2024-07-22 07:56:53', false), - (1, 'SECURITY_1', '2024-07-22 07:56:54', '2024-07-22 07:56:54', false), - (1, 'SECURITY_2', '2024-07-22 07:56:55', '2024-07-22 07:56:55', false), - (1, 'SECURITY_3', '2024-07-22 07:56:56', '2024-07-22 07:56:56', false); +INSERT INTO category (name) +VALUES + ('방 컨디션'), + ('창문'), + ('화장실'), + ('보안'), + ('외부'); + +INSERT INTO question (category_id, title, subtitle, is_default) +VALUES + (1, '곰팡이가 핀 곳 없이 깨끗한가요?', '천장, 벽면, 가구 뒤, 장판을 확인하세요.', true), + (1, '불쾌한 냄새 없이 쾌적한가요?', null, true), + (1, '벌레가 나온 흔적 없이 깔끔한가요?', '벌레 퇴치약이 부착되어 있는지 확인하세요.', true), + (1, '물건을 충분히 수납할 수 있는 공간이 있나요?', null, true), + (1, '방 인테리어는 괜찮나요?', null, true), + (1, '에어컨의 상태는 괜찮은가요?', '에어컨을 틀어서 불쾌한 냄새가 나진 않는지 확인하세요.', false), + (1, '보일러가 잘 동작하나요?', null, false), + (1, '콘센트 위치와 개수가 적절한가요?', null, false), + (1, '벽지 상태가 양호한가요?', null, false), + (2, '창 밖의 뷰가 가로막힘 없이 트여있나요?', null, true), + (2, '창문 상태가 괜찮나요?', null, true), + (2, '환기가 잘 되는 구조인가요?', '창문 크기와 방향을 확인하세요.', true), + (2, '햇빛이 잘 들어오나요?', null, true), + (2, '창문이 이중창인가요?', null, false), + (2, '창문 밖에 쓰레기통 등 냄새가 나는 요소가 있나요?', null, false), + (3, '화장실이 깨끗한가요?', '청소 가능한 얼룩인지 확인하세요.', true), + (3, '수압 및 물 빠짐이 괜찮은가요?', '화장실에서 수도와 변기를 동시에 사용해보세요.', true), + (3, '환기 시설이 있나요?', null, true), + (3, '내부에 창문이 있나요?', null, false), + (3, '온수가 잘 나오나요?', null, false), + (4, '잠금장치가 있는 공동 현관문이 있나요?', null, true), + (4, '출입구와 복도에 CCTV가 설치되어 있나요?', null, true), + (4, '관리자분이 함께 상주하시나요?', '관리자분이 24시간 상주하시는지 확인하세요.', true), + (4, '보안 시설이 잘 갖추어져 있나요?', '도어락, 창문 잠금장치 등이 있는지 확인하세요.', false), + (4, '화면이 달린 인터폰이 제공되나요?', null, false), + (4, '현관문에 걸쇠가 있나요?', null, false), + (5, '주변 도로가 밤에도 충분히 밝은가요?', null, false), + (5, '주변에 소음 시설이 있는지 확인했나요?', '유흥시설, 놀이터, 공사장이 있는지 확인하세요.', false), + (5, '1층에 음식점이 있는지 확인했나요?', null, false), + (5, '집 가는 길이 언덕 없이 완만한가요?', null, false), + (5, '옆 건물에서 보이는 구조인지 확인했나요?', null, false), + (5, '주차할 수 있는 시설이 있나요?', null, false) +; +INSERT INTO highlight (question_id, name) +VALUES + (1, '곰팡이'), + (2, '불쾌한 냄새'), + (3, '벌레'), + (4, '수납할 수 있는 공간'), + (5, '방 인테리어'), + (6, '에어컨'), + (7, '보일러'), + (8, '콘센트'), + (9, '벽지 상태'), + (10, '창 밖의 뷰'), + (11, '창문 상태'), + (12, '환기'), + (13, '햇빛'), + (14, '이중창'), + (15, '냄새가 나는 요소'), + (16, '깨끗'), + (17, '수압 및 물 빠짐'), + (18, '환기 시설'), + (19, '창문'), + (20, '온수'), + (21, '잠금장치'), + (21, '공동 현관문'), + (22, 'CCTV'), + (23, '관리자분'), + (24, '보안 시설'), + (25, '인터폰'), + (26, '걸쇠'), + (27, '주변 도로'), + (27, '밝은가요'), + (28, '소음 시설'), + (29, '음식점'), + (30, '언덕'), + (31, '보이는 구조'), + (32, '주차할 수 있는 시설') +; + +INSERT INTO custom_checklist_question(user_id, question, question_id, created_at, modified_at, deleted) +VALUES (1, 'ROOM_CONDITION_1',1, '2024-07-22 07:56:42', '2024-07-22 07:56:42', false), + (1, 'ROOM_CONDITION_2',2, '2024-07-22 07:56:43', '2024-07-22 07:56:43', false), + (1, 'ROOM_CONDITION_3',3, '2024-07-22 07:56:44', '2024-07-22 07:56:44', false), + (1, 'ROOM_CONDITION_4',4, '2024-07-22 07:56:45', '2024-07-22 07:56:45', false), + (1, 'ROOM_CONDITION_5',5, '2024-07-22 07:56:46', '2024-07-22 07:56:46', false), + (1, 'WINDOW_1',6, '2024-07-22 07:56:47', '2024-07-22 07:56:47', false), + (1, 'WINDOW_2',7, '2024-07-22 07:56:48', '2024-07-22 07:56:48', false), + (1, 'WINDOW_3',8, '2024-07-22 07:56:49', '2024-07-22 07:56:49', false), + (1, 'WINDOW_4',9, '2024-07-22 07:56:50', '2024-07-22 07:56:50', false), + (1, 'BATHROOM_1',10, '2024-07-22 07:56:51', '2024-07-22 07:56:51', false), + (1, 'BATHROOM_2',11, '2024-07-22 07:56:52', '2024-07-22 07:56:52', false), + (1, 'BATHROOM_3',12, '2024-07-22 07:56:53', '2024-07-22 07:56:53', false), + (1, 'SECURITY_1',13, '2024-07-22 07:56:54', '2024-07-22 07:56:54', false), + (1, 'SECURITY_2',14, '2024-07-22 07:56:55', '2024-07-22 07:56:55', false), + (1, 'SECURITY_3',15, '2024-07-22 07:56:56', '2024-07-22 07:56:56', false); INSERT INTO article(title, content, keyword, summary, thumbnail, created_at, modified_at, deleted) VALUES ('자취방 이사만 5번 피셜! 원룸 구할 때 체크리스트 1탄', - '학교 주변 자취방을 구하는 **대학생 분들,** 회사 주변 방을 구하는 **직장인 분들!** 그리고 그외 전국의 예비 자취생 여러분 모두 잘 오셨습니다.

원래 **자취방 구하기는 대학생 종강 시즌이 제철**이신 거, 다들 아시죠? 👀 그래서 오늘은 자취방 이사만 5번 해본 **자취방 구하기 만렙 에디터**가 찐 노하우가 담겨 있는 **원룸 구할 때 확인해야 할 체크리스트**를 가져왔습니다.

한 번도 생각해 보지 못했지만 **보자마자 납득 100%인 꿀팁들**을 고르고 골라 왔으니, 모두 집중하고 따라오시지요!



🪟 창문

**✅ 옆 건물에서 잘 보이는 구조인지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/160436720332165459.jpg)
많은 분들이 잘 생각하지 못하시는 부분 중 하나죠! **옆 건물과 사생활 보호가 되는지 여부**입니다. 물론 블라인드를 쳐서 가릴 수는 있지만, **매번 블라인드를 쳐둬야 하는 건** 은근 불편한 일이거든요! (=자취방 3호 경험담)

그리고 자취의 특권은 **샤워 후 자연의 상태로 나와서** 옷을 갈아입을 수 있다는 것 아니겠습니까? 🛀 그러니 자취의 장점을 누리기 위해 꼭 한번 체크해 보시는 게 좋겠습니다.

**✅ 환기하기에 적합한 크기인지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/159505656294119930.jpg)
자취방의 경우 그 특성상 창문이 하나만 있는 경우가 많은데요. 하지만 창문 전체가 시원하게 열리는 형태가 아니라, 위 사진처럼 **틈새만 열리는 경우라면 피하시는 게 좋습니다.**

좁은 평수일수록 환기가 매우 중요한데, 요런 창문의 경우에는 **환기 능력**이 떨어질 수밖에 없기 때문이죠! 결정 요인은 아니지만 **의외로 삶의 질과 연결**되는 부분이기 때문에 체크해 보시길 권장합니다.

**✅ 방충망/방범창 이상 없는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/164397041095518794.jpg)
창문 확인의 기본 of 기본! 바로 **방충망과 방범창 여부**입니다.

방충망은 구멍이 뚫려 **보수할 부분이 있는지**까지 체크해 주시는 게 좋고, 방범창은 **저층일수록 꼭** **확인**해 주시는 것이 좋습니다.

**✅ 햇빛 잘 들어오는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/166038643972068590.jpeg)
물론 집을 보러 가기 전에 남향인지 북향인지 중개인 분께서 설명을 해주실 텐데요. 하지만 남향이어도 앞 건물에 가려져 빛이 잘 안 들어올 수 있으니, 꼭 확인하기로 합시다.

**🍯 여기서 작은 꿀팁!**
Q. 앞 건물에 막힌 남향과 일반 북향/서향이 고민이라면?
A. 그래도 남향을 추천해요! 빛이 들어오지 않는 남향이라도 북향이나 서향보다는 훨씬 쾌적하답니다.

🔐 보안

**✅ 관리자 분 상주하시는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/167971937444371683.jpg)자취방 특성상 상시로 관리자 분이 계시는 경우가 많지는 않죠! 하지만 오피스텔의 경우에는 종종 관**리자분이 상주하고 계시는 경우**가 있는데요 👀

자취를 하다 보면 갑자기 불이 나가는 등 **전문가의 도움이 필요한 순간**들이 찾아오기 마련이죠. (그것의 자취의 맛..) 따라서 상주 관리자분이 계신 집이라면 **+15점** 해주시는 것이 좋겠습니다.

**✅ 현관문 잠금장치 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/167894935043362031.jpeg)
요것도 쉽게 지나칠 수 있는 포인트! 하지만 잠금장치 여부는 꼭 확인해 주시는 게 좋습니다. 자취를 하다 보면 정말 가끔 **모르는 사람이 초인종을 누르는 경우**도 있거든요 🤔

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
 ✅ 출입구와 복도에 CCTV 있는지
 ✅ 공동 현관 비밀번호 있는지

🏡 주변 환경

**✅ 무인 택배 보관함 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/166618977771822482.jpeg)
처음엔 굳이 필요한가 싶지만, **여기저기 활용도 좋은 무인 택배함!** 상세 주소 노출 없이 택배를 보관하기 좋은 것은 물론, 물건을 집에 두고 와야 하는데 **올라가기 귀찮을 때나 중고거래할 때** 등 유용하게 사용할 수 있어요.

만약 무인 택배함이 있는 집이라면 **보너스 점수**를 주기로 합시다 🕵️‍♀️

**✅ 대중교통 이용 편리한지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/168740647444591385.jpeg)
자취방을 구하는 목적은 바로 **학교나 직장 등에 편리하게 가기 위함**이 1번 아니겠습니까? 따라서 대중교통이 이용이 불편한 위치에 집이 있다면 **삶이 질이 수직 하락**할 수밖에 없죠!

지도상 시간도 체크하고, 실제로 자취방에서 대중교통 타는 곳까지 가보며 **경사나 주변 환경**도 체크해 보세요 🏃

**✅ 주변에 소음 시설 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/164194588552065716.jpeg)
아무래도 자취방 중에는 소음에 약한 곳이 많은데요! 따라서 **주변에 소음을 유발하는 시설이 있는지** 꼭 확인하셔야 합니다. 대표적으로는 **큰 도로와 술집** 정도가 있어요.

거기에 더해 꼭 확인하셔야 할 것은 바로 **24시 해장국집**입니다. 대개 **새벽까지 시끌시끌한 경우**가 많으므로 꼭 한번 확인해 보시길 바랍니다. (저도 알고 싶지 않았습니다)

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
 ✅ 편의점, 은행 등 편의시설 있는지
 ✅ 집 가는 길이 언덕인지
 ✅ 골목이라면 가로등 있는지

🛌 기본 옵션

**✅ 옵션 가구 치워줄 수 있는지 확인하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/165794935274391835.jpeg)
멀쩡한 옵션 가구를 왜 치우느냐! 하실 수 있지만, 자취를 하다 보면 의외로 **옵션 가구가 짐이 되는 경우**들이 많더라고요 🤔

에디터 또한 어느 날부터 옵션 책상이 불필요하고 너무 거슬려서 결국 집주인 분께 양해를 구하고 수거해 주시길 부탁드린 경우가 있답니다. 경우에 따라 집 주인분께서 거절하실 수도 있을 수 있으니 **꼭 사전에 확인**해 보세요!

**✅ 화구 종류 체크하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/167471560364252808.jpg) 집에서 요리를 종종 해 드시는 분이라면 꼭 **화구 종류를 확인**해 주세요! 요리를 즐겨 하시는 분일수록 가스레인지가 더 편리해요.

특히 하이라이트로 되어 있는 경우에는 **화력이 매우 약한 곳들**이 꽤나 있으니 ^^; 꼭 사전에 확인하시길 권합니다.

✅ **에어컨/냉장고 작동 점검하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168620136263807016.jpeg)
옵션 가구 중 **가장 고가인 에어컨과 냉장고**는 사전에 점검을 해두면 좋아요! 

만약 집 구하는 날 체크하지 못했다면, 혹시라도 문제가 생길 경우를 대비하여 **이사 후 일주일 내로 확인**해 보시길 바랍니다.

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 옵션 가구 필요 없다면 치워줄 수 있는지 확인하기
  ✅ 옵션 가구 종류 확인하기 (신발장, 블라인드 등)

지면 관계상 1탄은 여기까지~
2탄에서 이어서 만나요💟

출처 : 오늘의집', + '학교 주변 자취방을 구하는 **대학생 분들,** 회사 주변 방을 구하는 **직장인 분들!** 그리고 그외 전국의 예비 자취생 여러분 모두 잘 오셨습니다.

원래 **자취방 구하기는 대학생 종강 시즌이 제철**이신 거, 다들 아시죠? 👀 그래서 오늘은 자취방 이사만 5번 해본 **자취방 구하기 만렙 에디터**가 찐 노하우가 담겨 있는 **원룸 구할 때 확인해야 할 체크리스트**를 가져왔습니다.

한 번도 생각해 보지 못했지만 **보자마자 납득 100%인 꿀팁들**을 고르고 골라 왔으니, 모두 집중하고 따라오시지요!



🪟 창문

**✅ 옆 건물에서 잘 보이는 구조인지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/160436720332165459.jpg)
많은 분들이 잘 생각하지 못하시는 부분 중 하나죠! **옆 건물과 사생활 보호가 되는지 여부**입니다. 물론 블라인드를 쳐서 가릴 수는 있지만, **매번 블라인드를 쳐둬야 하는 건** 은근 불편한 일이거든요! (=자취방 3호 경험담)

그리고 자취의 특권은 **샤워 후 자연의 상태로 나와서** 옷을 갈아입을 수 있다는 것 아니겠습니까? 🛀 그러니 자취의 장점을 누리기 위해 꼭 한번 체크해 보시는 게 좋겠습니다.

**✅ 환기하기에 적합한 크기인지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/159505656294119930.jpg)
자취방의 경우 그 특성상 창문이 하나만 있는 경우가 많은데요. 하지만 창문 전체가 시원하게 열리는 형태가 아니라, 위 사진처럼 **틈새만 열리는 경우라면 피하시는 게 좋습니다.**

좁은 평수일수록 환기가 매우 중요한데, 요런 창문의 경우에는 **환기 능력**이 떨어질 수밖에 없기 때문이죠! 결정 요인은 아니지만 **의외로 삶의 질과 연결**되는 부분이기 때문에 체크해 보시길 권장합니다.

**✅ 방충망/방범창 이상 없는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/164397041095518794.jpg)
창문 확인의 기본 of 기본! 바로 **방충망과 방범창 여부**입니다.

방충망은 구멍이 뚫려 **보수할 부분이 있는지**까지 체크해 주시는 게 좋고, 방범창은 **저층일수록 꼭** **확인**해 주시는 것이 좋습니다.

**✅ 햇빛 잘 들어오는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/166038643972068590.jpeg)
물론 집을 보러 가기 전에 남향인지 북향인지 중개인 분께서 설명을 해주실 텐데요. 하지만 남향이어도 앞 건물에 가려져 빛이 잘 안 들어올 수 있으니, 꼭 확인하기로 합시다.

**🍯 여기서 작은 꿀팁!**
Q. 앞 건물에 막힌 남향과 일반 북향/서향이 고민이라면?
A. 그래도 남향을 추천해요! 빛이 들어오지 않는 남향이라도 북향이나 서향보다는 훨씬 쾌적하답니다.

🔐 보안

**✅ 관리자 분 상주하시는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/167971937444371683.jpg)자취방 특성상 상시로 관리자 분이 계시는 경우가 많지는 않죠! 하지만 오피스텔의 경우에는 종종 관**리자분이 상주하고 계시는 경우**가 있는데요 👀

자취를 하다 보면 갑자기 불이 나가는 등 **전문가의 도움이 필요한 순간**들이 찾아오기 마련이죠. (그것의 자취의 맛..) 따라서 상주 관리자분이 계신 집이라면 **+15점** 해주시는 것이 좋겠습니다.

**✅ 현관문 잠금장치 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/167894935043362031.jpeg)
요것도 쉽게 지나칠 수 있는 포인트! 하지만 잠금장치 여부는 꼭 확인해 주시는 게 좋습니다. 자취를 하다 보면 정말 가끔 **모르는 사람이 초인종을 누르는 경우**도 있거든요 🤔

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
 ✅ 출입구와 복도에 CCTV 있는지
 ✅ 공동 현관 비밀번호 있는지

🏡 주변 환경

**✅ 무인 택배 보관함 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/166618977771822482.jpeg)
처음엔 굳이 필요한가 싶지만, **여기저기 활용도 좋은 무인 택배함!** 상세 주소 노출 없이 택배를 보관하기 좋은 것은 물론, 물건을 집에 두고 와야 하는데 **올라가기 귀찮을 때나 중고거래할 때** 등 유용하게 사용할 수 있어요.

만약 무인 택배함이 있는 집이라면 **보너스 점수**를 주기로 합시다 🕵️‍♀️

**✅ 대중교통 이용 편리한지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/168740647444591385.jpeg)
자취방을 구하는 목적은 바로 **학교나 직장 등에 편리하게 가기 위함**이 1번 아니겠습니까? 따라서 대중교통이 이용이 불편한 위치에 집이 있다면 **삶이 질이 수직 하락**할 수밖에 없죠!

지도상 시간도 체크하고, 실제로 자취방에서 대중교통 타는 곳까지 가보며 **경사나 주변 환경**도 체크해 보세요 🏃

**✅ 주변에 소음 시설 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/164194588552065716.jpeg)
아무래도 자취방 중에는 소음에 약한 곳이 많은데요! 따라서 **주변에 소음을 유발하는 시설이 있는지** 꼭 확인하셔야 합니다. 대표적으로는 **큰 도로와 술집** 정도가 있어요.

거기에 더해 꼭 확인하셔야 할 것은 바로 **24시 해장국집**입니다. 대개 **새벽까지 시끌시끌한 경우**가 많으므로 꼭 한번 확인해 보시길 바랍니다. (저도 알고 싶지 않았습니다)

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
 ✅ 편의점, 은행 등 편의시설 있는지
 ✅ 집 가는 길이 언덕인지
 ✅ 골목이라면 가로등 있는지

🛌 기본 옵션

**✅ 옵션 가구 치워줄 수 있는지 확인하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/165794935274391835.jpeg)
멀쩡한 옵션 가구를 왜 치우느냐! 하실 수 있지만, 자취를 하다 보면 의외로 **옵션 가구가 짐이 되는 경우**들이 많더라고요 🤔

에디터 또한 어느 날부터 옵션 책상이 불필요하고 너무 거슬려서 결국 집주인 분께 양해를 구하고 수거해 주시길 부탁드린 경우가 있답니다. 경우에 따라 집 주인분께서 거절하실 수도 있을 수 있으니 **꼭 사전에 확인**해 보세요!

**✅ 화구 종류 체크하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/167471560364252808.jpg) 집에서 요리를 종종 해 드시는 분이라면 꼭 **화구 종류를 확인**해 주세요! 요리를 즐겨 하시는 분일수록 가스레인지가 더 편리해요.

특히 하이라이트로 되어 있는 경우에는 **화력이 매우 약한 곳들**이 꽤나 있으니 ^^; 꼭 사전에 확인하시길 권합니다.

✅ **에어컨/냉장고 작동 점검하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168620136263807016.jpeg)
옵션 가구 중 **가장 고가인 에어컨과 냉장고**는 사전에 점검을 해두면 좋아요!

만약 집 구하는 날 체크하지 못했다면, 혹시라도 문제가 생길 경우를 대비하여 **이사 후 일주일 내로 확인**해 보시길 바랍니다.

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 옵션 가구 필요 없다면 치워줄 수 있는지 확인하기
  ✅ 옵션 가구 종류 확인하기 (신발장, 블라인드 등)

지면 관계상 1탄은 여기까지~
2탄에서 이어서 만나요💟

출처 : 오늘의집', '자취방 꿀팁', '원룸 체크리스트', 'https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/160436720332165459.jpg', @@ -28,7 +112,7 @@ VALUES ('자취방 이사만 5번 피셜! 원룸 구할 때 체크리스트 1탄 '2024-07-22 07:56:42', false), ('자취방 이사만 5번 피셜! 원룸 구할 때 체크리스트 2탄', - '안녕하세요~ **자취방 구하기 만렙 에디터**가 찐 노하우가 담겨 있는 **원룸 구할 때 확인해야 할 체크리스트 2탄**으로 돌아왔습니다!

1탄을 아직 못보신 분들은 먼저 확인하고 돌아와주세요. 다들 집중하시고 가봅시다!!

🕵️ 디테일

**✅ 인터폰 영상 지원되는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168217562696278839.jpeg)
이것도 앞서 이야기해 드린 **현관문 잠금장치와 비슷한 맥락**인데요! **배달 음식이나 등기 수령** 같은 경우에도 음성 인터폰만 듣는 것보다는 영상을 통해 방문자를 확인하는 것이 안전하죠.

잘 모르는 상대가 찾아왔을 때, **상대방의 모습을 확인**할 수 있다는 것만으로도 **마음의 안정에 큰 도움**이 되니 확인해 보시는 걸 추천드립니다. (방문 기록을 증거로 남길 수 있는 건 덤!)


**✅ 바퀴벌레 약 설치되어 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/159404181119534587.jpeg)
만약 집을 보러 갔는데 가구 뒤, 신발장 옆, 화장실 변기 뒤쪽 등등 **바퀴벌레 약을 설치한 흔적**이 있다? **웬만하면 런하시기를 추천합니다** 🏃‍♂️

물론 현재 거주자분이 꼼꼼한 성격으로 사전 예방하신 경우일 수도 있지만, 대부분 높은 확률로 한차례 발견했기 때문에 설치하는 경우가 많기 때문이죠...😱 가능한 도망치시기를!


**✅ 벽지에 곰팡이 흔적 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168645987277346900.jpeg)
곰팡이가 잘 생기는 **창문 주변 벽지와 침대 뒤쪽** 등, **시커먼 곰팡이 흔적**이 있는지 확인해 주세요.

곰팡이 흔적이 심하게 남아 있다면 높은 확률로 **습도 관리가 어려운 집**이거나 **집 구조적으로 곰팡이가 생길 수밖에 없는 곳**이랍니다. 따라서 이 경우에도 빠르게 런하시길! 🏃‍♂️

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 콘센트 개수는 충분한지
  ✅ 옆집 방음 잘 되는지


✍🏻 기타 사항

**✅ 건물에 집주인분 사시는지 확인하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/156680003862457833.jpg)
사소하지만 은근 중요한 디테일! 바로 **집주인 분과 같은 건물에서 살게 되는지**를 체크해 주시면 좋습니다.

대학가 인근 빌라의 경우 집주인 분도 같은 건물에 거주하시는 경우가 종종 있는데요. **이게 은근 불편하고 머쓱하다는 사실..!** 물론 개인차는 있겠지만 본인이 에디터의 경우에 해당된다면? 은밀하게 체크해 주세요.


**✅ 분리수거 시스템 확인하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/165413921573933477.jpeg)
**분리 수거 시스템이 체계적으로 운영**되고 있는지, **쓰레기 버리기에는 수월한지**도 확인해 주시면 좋습니다.

자취방 중에 간혹 굉장히 불편하고 비효율적인 분리수거 시스템을 가진 곳들이 있거든요! **배출 일자나 주기적으로 쓰레기 관리해 주시는 분이 계시는지 여부** 등등 면밀하게 체크하시는 게 좋겠습니다.

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 관리비 포함 항목 확인하기
  ✅ 인테리어 가능 여부 체크하기

🚽 화장실

**✅ 배수구 냄새 올라오는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/163777210096433394.jpg)
**화장실 숙적!** 바로 **배수구 냄새**죠.특히 더워지는 여름철엔 모두가 고민하는 부분이긴 하지만, **유달리 배수구 냄새가 지독하게 나는 집**이 있다는 사실!

이 부분은 정말 삶의 질과 직결되기 때문에, **화장실에 들어가 문을 닫고** 꼭 한번 확인해 주세요 😉


**✅ 화장실 내부에 창문 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/161331910819056750.jpg)
흔한 조건은 아니지만 **있다면 무조건 플러스 오십 점인 화장실 창문!**

창문이 없다면 환풍기는 잘 되는지, 방문 당시 화장실 습도는 어느 정도인지 확인해 주시는 편이 좋습니다 🌬️

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 샤워 여유 공간 충분한지
  ✅ 곰팡이 흔적 있는지


🚿 수도와 배수

**✅ 싱크대/화장실 배수구 잘 내려가는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/167694880830634529.jpeg)
곳곳의 수전에서 물이 잘 나오는지를 확인하는 것과 더불어, 꼭 챙겨야 할 것은 **배수구로 물이 내려가는 속도**입니다. **특히 세면대의 경우**가 각종 이물질이 축적되어 물이 매우 늦게 내려가는 경우가 있어요! (= 2호 자취방)

빠르게 설거지나 빨래를 해야 하는데 물이 잘 안 내려가면 정말 성격 안 좋아지기 때문에 🤯 화장실과 싱크대 모두 꼼꼼히 체크하시길 권합니다.


**✅ 변기 물 잘 내려가는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/166970947851460229.jpg)
여기서 포인트는 **시원하고 우렁차게!** 입니다. 종종 변기 내려가는 힘이 매우 약한 곳들이 있는데요.

이 경우 삶의 질이 급격히 저하되므로, **변기 내릴 때 소리와 시원함**을 꼭! 체크하시길 바랍니다.

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
 ✅ 싱크대/세면대/샤워기 물 잘 나오는지
 ✅ 싱크대/화장실 온수 잘 나오는지


오늘은 자취 이사만 5번 경력을 살려 고인물이 아니면 알 수 없는 원룸 구할 때 체크할 것들에 대해 소개해 드렸는데요!
자취방 구할 때 꼭 요긴하게 사용하시길 바랍니다 💟

출처 : 오늘의집', + '안녕하세요~ **자취방 구하기 만렙 에디터**가 찐 노하우가 담겨 있는 **원룸 구할 때 확인해야 할 체크리스트 2탄**으로 돌아왔습니다!

1탄을 아직 못보신 분들은 먼저 확인하고 돌아와주세요. 다들 집중하시고 가봅시다!!

🕵️ 디테일

**✅ 인터폰 영상 지원되는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168217562696278839.jpeg)
이것도 앞서 이야기해 드린 **현관문 잠금장치와 비슷한 맥락**인데요! **배달 음식이나 등기 수령** 같은 경우에도 음성 인터폰만 듣는 것보다는 영상을 통해 방문자를 확인하는 것이 안전하죠.

잘 모르는 상대가 찾아왔을 때, **상대방의 모습을 확인**할 수 있다는 것만으로도 **마음의 안정에 큰 도움**이 되니 확인해 보시는 걸 추천드립니다. (방문 기록을 증거로 남길 수 있는 건 덤!)


**✅ 바퀴벌레 약 설치되어 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/159404181119534587.jpeg)
만약 집을 보러 갔는데 가구 뒤, 신발장 옆, 화장실 변기 뒤쪽 등등 **바퀴벌레 약을 설치한 흔적**이 있다? **웬만하면 런하시기를 추천합니다** 🏃‍♂️

물론 현재 거주자분이 꼼꼼한 성격으로 사전 예방하신 경우일 수도 있지만, 대부분 높은 확률로 한차례 발견했기 때문에 설치하는 경우가 많기 때문이죠...😱 가능한 도망치시기를!


**✅ 벽지에 곰팡이 흔적 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168645987277346900.jpeg)
곰팡이가 잘 생기는 **창문 주변 벽지와 침대 뒤쪽** 등, **시커먼 곰팡이 흔적**이 있는지 확인해 주세요.

곰팡이 흔적이 심하게 남아 있다면 높은 확률로 **습도 관리가 어려운 집**이거나 **집 구조적으로 곰팡이가 생길 수밖에 없는 곳**이랍니다. 따라서 이 경우에도 빠르게 런하시길! 🏃‍♂️

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 콘센트 개수는 충분한지
  ✅ 옆집 방음 잘 되는지


✍🏻 기타 사항

**✅ 건물에 집주인분 사시는지 확인하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/156680003862457833.jpg)
사소하지만 은근 중요한 디테일! 바로 **집주인 분과 같은 건물에서 살게 되는지**를 체크해 주시면 좋습니다.

대학가 인근 빌라의 경우 집주인 분도 같은 건물에 거주하시는 경우가 종종 있는데요. **이게 은근 불편하고 머쓱하다는 사실..!** 물론 개인차는 있겠지만 본인이 에디터의 경우에 해당된다면? 은밀하게 체크해 주세요.


**✅ 분리수거 시스템 확인하기**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/165413921573933477.jpeg)
**분리 수거 시스템이 체계적으로 운영**되고 있는지, **쓰레기 버리기에는 수월한지**도 확인해 주시면 좋습니다.

자취방 중에 간혹 굉장히 불편하고 비효율적인 분리수거 시스템을 가진 곳들이 있거든요! **배출 일자나 주기적으로 쓰레기 관리해 주시는 분이 계시는지 여부** 등등 면밀하게 체크하시는 게 좋겠습니다.

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 관리비 포함 항목 확인하기
  ✅ 인테리어 가능 여부 체크하기

🚽 화장실

**✅ 배수구 냄새 올라오는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/163777210096433394.jpg)
**화장실 숙적!** 바로 **배수구 냄새**죠.특히 더워지는 여름철엔 모두가 고민하는 부분이긴 하지만, **유달리 배수구 냄새가 지독하게 나는 집**이 있다는 사실!

이 부분은 정말 삶의 질과 직결되기 때문에, **화장실에 들어가 문을 닫고** 꼭 한번 확인해 주세요 😉


**✅ 화장실 내부에 창문 있는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/161331910819056750.jpg)
흔한 조건은 아니지만 **있다면 무조건 플러스 오십 점인 화장실 창문!**

창문이 없다면 환풍기는 잘 되는지, 방문 당시 화장실 습도는 어느 정도인지 확인해 주시는 편이 좋습니다 🌬️

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
  ✅ 샤워 여유 공간 충분한지
  ✅ 곰팡이 흔적 있는지


🚿 수도와 배수

**✅ 싱크대/화장실 배수구 잘 내려가는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/167694880830634529.jpeg)
곳곳의 수전에서 물이 잘 나오는지를 확인하는 것과 더불어, 꼭 챙겨야 할 것은 **배수구로 물이 내려가는 속도**입니다. **특히 세면대의 경우**가 각종 이물질이 축적되어 물이 매우 늦게 내려가는 경우가 있어요! (= 2호 자취방)

빠르게 설거지나 빨래를 해야 하는데 물이 잘 안 내려가면 정말 성격 안 좋아지기 때문에 🤯 화장실과 싱크대 모두 꼼꼼히 체크하시길 권합니다.


**✅ 변기 물 잘 내려가는지**
![img](https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/projects/166970947851460229.jpg)
여기서 포인트는 **시원하고 우렁차게!** 입니다. 종종 변기 내려가는 힘이 매우 약한 곳들이 있는데요.

이 경우 삶의 질이 급격히 저하되므로, **변기 내릴 때 소리와 시원함**을 꼭! 체크하시길 바랍니다.

**🙋‍♀️ 추가로 이것도 확인해 보세요!**
 ✅ 싱크대/세면대/샤워기 물 잘 나오는지
 ✅ 싱크대/화장실 온수 잘 나오는지


오늘은 자취 이사만 5번 경력을 살려 고인물이 아니면 알 수 없는 원룸 구할 때 체크할 것들에 대해 소개해 드렸는데요!
자취방 구할 때 꼭 요긴하게 사용하시길 바랍니다 💟

출처 : 오늘의집', '자취방 꿀팁', '원룸 체크리스트', 'https://image.ohou.se/i/bucketplace-v2-development/uploads/cards/snapshots/168217562696278839.jpeg', @@ -36,7 +120,7 @@ VALUES ('자취방 이사만 5번 피셜! 원룸 구할 때 체크리스트 1탄 '2024-07-22 07:56:42', false), ('초보 세입자들에게 전하는 부동산 계약 팁', - '마음에 쏙 드는 방을 찾았다면 이젠 집을 계약할 단계겠죠? 이번 포스트에서는 초보 세입자분들을 위해서 방을 계약할 때 참고해야 할 사항들을 말씀드리도록 하겠습니다.

등기부등본, 건축물대장 열람은 필수!

등기부등본과 건축물대장은 해당 부동산, 건물에 대해서 소유권자에 대한 사항과 위치·면적·구조·용도·층수 등에 대한 사항을 알 수 있는 문서인데요. 등기부등본과 건축물대장을 통해서 **계약하려는 방, 건물의 소유주가 실제 계약을 진행하는 임대인과 동일한지, 건물 또한 등록된 내용과 다른 부분이 없는지** 확인해야 합니다.

특히, 등기부등본에는 부동산에 대해 설정된 융자, 경매, 저당권에 대해서도 확인할 수 있으니 반드시 확인하고 계약을 진행해야 합니다. 혹시나 등기부등본과 건축물대장에 표시된 내용이 다를 수 있는데 권리관계는 등기부등본을 기준으로 하며 부동산의 표시는 건축물대장을 기준으로 하게 되기 때문에 이를 참고하시고, 혹시 표시가 달라 걱정이 된다면 인터넷 등기소를 통해 수정을 하시면 됩니다.

계약은 집주인과 하는 것이다!

계약은 집주인이 임대인이고 방을 구하는 여러분이 임차인이 되어 진행되는 것입니다. **계약의 당사자는 반드시 본인과 집주인이 되어야 한다는 것**인데요. 보통은 방을 구하는 당사자와 집주인, 중개인 이렇게 계약을 진행하겠지만 가끔 특별한 사유로 집주인(임대인)이 직접 자리하여 계약을 진행하지 못하는 경우가 발생할 수 있습니다. 이때, 임대인은 대리인에게 본인의 직인과 대리인 임명장을 통해서 실제로 권한을 부여한 대리인임을 증명할 수 있고 대리인은 부여된 권한 안에서 대리계약을 진행할 수 있습니다.

주의할 것은, 대리인은 계약에서 집주인으로부터 권한을 부여받은 대리인일 뿐, 계약 당사자가 아니기 때문에 계약서상의 성명 직인은 모두 집주인인 임대인의 것이 들어가야 하며 계약금도 집주인이 별도로 말한 내용이 없다면 집주인 명의의 계좌로 입금해야 합니다.

협의된 내용은 특약사항에 명시할 것!

집을 계약할 때 집주인과 얘기하여 협의한 내용들(옵션사항, 월세 및 관리비 관련 사항, 기타 생활규칙, 내부 개조 등)은 계약서상에 명시가 안된 경우가 많기 때문에 이러한 내용들은 특약사항으로 따로 적어주는 것이 좋습니다.

집주인과 얘기가 다 되었다고 생각하여 이를 계약서 상에 기재하지 않았을 경우 문제가 발생했을 때, 이전에 집주인과 협의된 사항임을 본인이 직접 밝혀야 하기 때문에 **임차인이 적극적으로 특약사항을 확인하고 내용을 명시**해야 합니다.

전입신고 및 확정일자를 받을 것!

전입신고와 확정일자는 임차인이 임대차계약에서 법적으로 권리를 보호받을 수 있는 절차입니다. 전입신고는 새로운 거주지에 전입하여 주소지 변경 사항을 알리는 절차이며 이를 통해 주택임대차보호법의 보호를 받을 수 있고, 확정일자는 임대차계약을 맺은 날짜를 확인하는 것으로 경매 등의 상황에서 우선적으로 보증금을 변제받을 수 있게 됩니다.

전입신고와 확정일자는 주민센터에서 바로 민원이 가능하며 전입신고와 동시에 확정일자를 받는 것이 가장 편리한 방법이며 **신고는 전입한 날로부터 14일 이내로 완료**하면 됩니다.

출처:https://brunch.co.kr/@dprnrn234/124', + '마음에 쏙 드는 방을 찾았다면 이젠 집을 계약할 단계겠죠? 이번 포스트에서는 초보 세입자분들을 위해서 방을 계약할 때 참고해야 할 사항들을 말씀드리도록 하겠습니다.

등기부등본, 건축물대장 열람은 필수!

등기부등본과 건축물대장은 해당 부동산, 건물에 대해서 소유권자에 대한 사항과 위치·면적·구조·용도·층수 등에 대한 사항을 알 수 있는 문서인데요. 등기부등본과 건축물대장을 통해서 **계약하려는 방, 건물의 소유주가 실제 계약을 진행하는 임대인과 동일한지, 건물 또한 등록된 내용과 다른 부분이 없는지** 확인해야 합니다.

특히, 등기부등본에는 부동산에 대해 설정된 융자, 경매, 저당권에 대해서도 확인할 수 있으니 반드시 확인하고 계약을 진행해야 합니다. 혹시나 등기부등본과 건축물대장에 표시된 내용이 다를 수 있는데 권리관계는 등기부등본을 기준으로 하며 부동산의 표시는 건축물대장을 기준으로 하게 되기 때문에 이를 참고하시고, 혹시 표시가 달라 걱정이 된다면 인터넷 등기소를 통해 수정을 하시면 됩니다.

계약은 집주인과 하는 것이다!

계약은 집주인이 임대인이고 방을 구하는 여러분이 임차인이 되어 진행되는 것입니다. **계약의 당사자는 반드시 본인과 집주인이 되어야 한다는 것**인데요. 보통은 방을 구하는 당사자와 집주인, 중개인 이렇게 계약을 진행하겠지만 가끔 특별한 사유로 집주인(임대인)이 직접 자리하여 계약을 진행하지 못하는 경우가 발생할 수 있습니다. 이때, 임대인은 대리인에게 본인의 직인과 대리인 임명장을 통해서 실제로 권한을 부여한 대리인임을 증명할 수 있고 대리인은 부여된 권한 안에서 대리계약을 진행할 수 있습니다.

주의할 것은, 대리인은 계약에서 집주인으로부터 권한을 부여받은 대리인일 뿐, 계약 당사자가 아니기 때문에 계약서상의 성명 직인은 모두 집주인인 임대인의 것이 들어가야 하며 계약금도 집주인이 별도로 말한 내용이 없다면 집주인 명의의 계좌로 입금해야 합니다.

협의된 내용은 특약사항에 명시할 것!

집을 계약할 때 집주인과 얘기하여 협의한 내용들(옵션사항, 월세 및 관리비 관련 사항, 기타 생활규칙, 내부 개조 등)은 계약서상에 명시가 안된 경우가 많기 때문에 이러한 내용들은 특약사항으로 따로 적어주는 것이 좋습니다.

집주인과 얘기가 다 되었다고 생각하여 이를 계약서 상에 기재하지 않았을 경우 문제가 발생했을 때, 이전에 집주인과 협의된 사항임을 본인이 직접 밝혀야 하기 때문에 **임차인이 적극적으로 특약사항을 확인하고 내용을 명시**해야 합니다.

전입신고 및 확정일자를 받을 것!

전입신고와 확정일자는 임차인이 임대차계약에서 법적으로 권리를 보호받을 수 있는 절차입니다. 전입신고는 새로운 거주지에 전입하여 주소지 변경 사항을 알리는 절차이며 이를 통해 주택임대차보호법의 보호를 받을 수 있고, 확정일자는 임대차계약을 맺은 날짜를 확인하는 것으로 경매 등의 상황에서 우선적으로 보증금을 변제받을 수 있게 됩니다.

전입신고와 확정일자는 주민센터에서 바로 민원이 가능하며 전입신고와 동시에 확정일자를 받는 것이 가장 편리한 방법이며 **신고는 전입한 날로부터 14일 이내로 완료**하면 됩니다.

출처:https://brunch.co.kr/@dprnrn234/124', '등기부등본', '방 계약 시 주의할 점', null, @@ -44,7 +128,7 @@ VALUES ('자취방 이사만 5번 피셜! 원룸 구할 때 체크리스트 1탄 '2024-07-22 07:56:42', false), ('전세 사기 피하는 법…계약 전 꼭 확인해야 할 8가지', - '![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/whHWpgBBQsZMIKotGenBwFIKcPERqYKu.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/yyxhNCPoGKUFhtSmnxCNAxFHovdrREfW.png)

전세사기 예방, 꼭! 확인해야 할 8가지!


압류 및 세금체납 등 권리제한사항이 있거나 매매가격보다 과도하게 높은 전세가격 등으로 **보증금을 돌려받지 못하는 전세사기!**

깡통전세·전세사기 피해자가 더 늘어나지 않도록 하기 위해 예방 대책 및 지원 방안을 마련하고 총력을 기울이고 있는데요. **전세사기 예방을 위해 꼭 확인해야 할 8가지, 함께 살펴보아요!** ![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/ZrgaSqFihDwrSiXzxxUVzIgjcohGoGCe.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/GYuqUxJtzxeFpHrWtBObYmzYPIqySThn.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/TFWMcSjDHnkEDiBGBmMcRVxpRhDPRkbP.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/cYHtopKhdHyBhbFWpNodygWStpjCVmUZ.png)

출처:https://mediahub.seoul.go.kr/archives/2007948', + '![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/whHWpgBBQsZMIKotGenBwFIKcPERqYKu.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/yyxhNCPoGKUFhtSmnxCNAxFHovdrREfW.png)

전세사기 예방, 꼭! 확인해야 할 8가지!


압류 및 세금체납 등 권리제한사항이 있거나 매매가격보다 과도하게 높은 전세가격 등으로 **보증금을 돌려받지 못하는 전세사기!**

깡통전세·전세사기 피해자가 더 늘어나지 않도록 하기 위해 예방 대책 및 지원 방안을 마련하고 총력을 기울이고 있는데요. **전세사기 예방을 위해 꼭 확인해야 할 8가지, 함께 살펴보아요!** ![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/ZrgaSqFihDwrSiXzxxUVzIgjcohGoGCe.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/GYuqUxJtzxeFpHrWtBObYmzYPIqySThn.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/TFWMcSjDHnkEDiBGBmMcRVxpRhDPRkbP.png)
![img](https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/cYHtopKhdHyBhbFWpNodygWStpjCVmUZ.png)

출처:https://mediahub.seoul.go.kr/archives/2007948', '전세', '전세 사기 피하는 법', 'https://mediahub.seoul.go.kr/uploads/mediahub/2023/05/whHWpgBBQsZMIKotGenBwFIKcPERqYKu.png', diff --git a/backend/bang-ggood/src/main/resources/schema.sql b/backend/bang-ggood/src/main/resources/schema.sql index de0f8bad6..62faa28b2 100644 --- a/backend/bang-ggood/src/main/resources/schema.sql +++ b/backend/bang-ggood/src/main/resources/schema.sql @@ -1,15 +1,44 @@ -- Drop tables if they exist +DROP TABLE IF EXISTS checklist_station CASCADE; +DROP TABLE IF EXISTS checklist_like CASCADE; +DROP TABLE IF EXISTS custom_checklist_question CASCADE; DROP TABLE IF EXISTS checklist_option CASCADE; DROP TABLE IF EXISTS checklist_question CASCADE; -DROP TABLE IF EXISTS article CASCADE; -DROP TABLE IF EXISTS checklist_like CASCADE; DROP TABLE IF EXISTS checklist_maintenance CASCADE; DROP TABLE IF EXISTS checklist CASCADE; -DROP TABLE IF EXISTS room CASCADE; -DROP TABLE IF EXISTS custom_checklist_question CASCADE; +DROP TABLE IF EXISTS article CASCADE; DROP TABLE IF EXISTS users CASCADE; +DROP TABLE IF EXISTS room CASCADE; +DROP TABLE IF EXISTS highlight CASCADE; +DROP TABLE IF EXISTS question CASCADE; +DROP TABLE IF EXISTS category CASCADE; -- Create tables + +CREATE TABLE category +( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) +); + +CREATE TABLE question +( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + category_id INTEGER, + title VARCHAR(255), + subtitle VARCHAR(255), + is_default BOOLEAN, + FOREIGN KEY (category_id) REFERENCES category (id) +); + +CREATE TABLE highlight +( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + question_id INTEGER, + name VARCHAR(255), + FOREIGN KEY (question_id) REFERENCES question (id) +); + CREATE TABLE room ( id BIGINT AUTO_INCREMENT PRIMARY KEY, @@ -32,10 +61,13 @@ CREATE TABLE users id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255) NOT NULL, - type VARCHAR(255) NOT NULL, + password VARCHAR(255), + user_type VARCHAR(255) NOT NULL, + login_type VARCHAR(255) NOT NULL, created_at TIMESTAMP(6), modified_at TIMESTAMP(6), - deleted BOOLEAN + deleted BOOLEAN, + CONSTRAINT unique_email_login_type UNIQUE (email, login_type) ); CREATE TABLE checklist @@ -59,18 +91,30 @@ CREATE TABLE checklist FOREIGN KEY (user_id) REFERENCES users (id) ); +CREATE TABLE checklist_maintenance +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + checklist_id BIGINT, + maintenance_item VARCHAR(255), + created_at TIMESTAMP(6), + modified_at TIMESTAMP(6), + deleted BOOLEAN, + FOREIGN KEY (checklist_id) REFERENCES checklist (id) +); + CREATE TABLE checklist_question ( id BIGINT AUTO_INCREMENT PRIMARY KEY, question VARCHAR(255) NOT NULL, + question_id INTEGER NOT NULL, checklist_id BIGINT NOT NULL, answer VARCHAR(255), created_at TIMESTAMP(6), modified_at TIMESTAMP(6), deleted BOOLEAN, - FOREIGN KEY (checklist_id) REFERENCES checklist (id) + FOREIGN KEY (checklist_id) REFERENCES checklist (id), + FOREIGN KEY (question_id) REFERENCES question (id) ); - CREATE TABLE checklist_option ( id BIGINT AUTO_INCREMENT PRIMARY KEY, @@ -87,10 +131,12 @@ CREATE TABLE custom_checklist_question id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT, question VARCHAR(255), + question_id INTEGER, created_at TIMESTAMP(6), modified_at TIMESTAMP(6), deleted BOOLEAN, - FOREIGN KEY (user_id) references users (id) + FOREIGN KEY (user_id) references users (id), + FOREIGN KEY (question_id) references question (id) ); CREATE TABLE checklist_like @@ -103,17 +149,6 @@ CREATE TABLE checklist_like FOREIGN KEY (checklist_id) REFERENCES checklist (id) ); -CREATE TABLE checklist_maintenance -( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - checklist_id BIGINT, - maintenance_item VARCHAR(255), - created_at TIMESTAMP(6), - modified_at TIMESTAMP(6), - deleted BOOLEAN, - FOREIGN KEY (checklist_id) REFERENCES checklist (id) -); - CREATE TABLE article ( id BIGINT AUTO_INCREMENT PRIMARY KEY, @@ -126,3 +161,17 @@ CREATE TABLE article modified_at TIMESTAMP(6), deleted BOOLEAN ); + +CREATE TABLE checklist_station +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + checklist_id BIGINT, + station_name VARCHAR(255), + station_line VARCHAR(255), + walking_time INTEGER, + created_at TIMESTAMP(6), + modified_at TIMESTAMP(6), + deleted BOOLEAN, + FOREIGN KEY (checklist_id) REFERENCES checklist (id) +); + diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/AcceptanceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/AcceptanceTest.java index 7b77e96c3..1059973e7 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/AcceptanceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/AcceptanceTest.java @@ -1,15 +1,18 @@ package com.bang_ggood; -import com.bang_ggood.auth.controller.CookieProvider; -import com.bang_ggood.auth.service.JwtTokenProvider; +import com.bang_ggood.auth.controller.cookie.CookieProvider; +import com.bang_ggood.auth.service.jwt.JwtTokenProvider; import com.bang_ggood.user.UserFixture; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.repository.UserRepository; import io.restassured.RestAssured; +import io.restassured.http.Header; +import io.restassured.http.Headers; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; @@ -18,18 +21,16 @@ @ActiveProfiles("test") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Sql(scripts = {"/schema-test.sql", "/data-test.sql"}) -public abstract class AcceptanceTest { +public abstract class AcceptanceTest extends IntegrationTestSupport { + protected Headers headers; @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private CookieProvider cookieProvider; @Autowired private UserRepository userRepository; - private User authenticatedUser; - protected ResponseCookie responseCookie; - @LocalServerPort private int port; @@ -44,9 +45,14 @@ private void setPort() { } private void setResponseCookie() { - authenticatedUser = userRepository.save(UserFixture.USER1()); - String token = jwtTokenProvider.createAccessToken(authenticatedUser); - responseCookie = cookieProvider.createAccessTokenCookie(token); + authenticatedUser = userRepository.save(UserFixture.USER1); + String accessToken = jwtTokenProvider.createAccessToken(authenticatedUser); + String refreshToken = jwtTokenProvider.createRefreshToken(authenticatedUser); + ResponseCookie accessTokenResponseCookie = cookieProvider.createAccessTokenCookie(accessToken); + ResponseCookie refreshTokenCookie = cookieProvider.createRefreshTokenCookie(refreshToken); + + headers = new Headers(new Header(HttpHeaders.COOKIE, accessTokenResponseCookie.toString()), + new Header(HttpHeaders.COOKIE, refreshTokenCookie.toString())); } public User getAuthenticatedUser() { diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/IntegrationTestSupport.java b/backend/bang-ggood/src/test/java/com/bang_ggood/IntegrationTestSupport.java index c02b03a44..446b4a654 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/IntegrationTestSupport.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/IntegrationTestSupport.java @@ -1,5 +1,13 @@ package com.bang_ggood; +import com.bang_ggood.question.CustomChecklistFixture; +import com.bang_ggood.question.QuestionFixture; +import com.bang_ggood.question.repository.CategoryRepository; +import com.bang_ggood.question.repository.QuestionRepository; +import com.bang_ggood.user.UserFixture; +import com.bang_ggood.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.ActiveProfiles; @@ -9,4 +17,20 @@ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @Sql(scripts = {"/schema-test.sql", "/data-test.sql"}) public abstract class IntegrationTestSupport { + + @Autowired + CategoryRepository categoryRepository; + + @Autowired + QuestionRepository questionRepository; + + @Autowired + UserRepository userRepository; + + @BeforeEach + void init() { + UserFixture.init(userRepository); + QuestionFixture.init(categoryRepository, questionRepository); + CustomChecklistFixture.init(); + } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/JpaAuditingTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/JpaAuditingTest.java index 6425cb9ab..0fa721a46 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/JpaAuditingTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/JpaAuditingTest.java @@ -29,8 +29,8 @@ void jpaAuditing() { // then assertAll( - () -> assertThat(savedEntity.getCreatedAt()).isBefore(LocalDateTime.now()), - () -> assertThat(savedEntity.getModifiedAt()).isBefore(LocalDateTime.now()) + () -> assertThat(savedEntity.getCreatedAt()).isBeforeOrEqualTo(LocalDateTime.now()), + () -> assertThat(savedEntity.getModifiedAt()).isBeforeOrEqualTo(LocalDateTime.now()) ); } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/article/controller/ArticleE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/article/controller/ArticleE2ETest.java index 320b83126..28c9b92dc 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/article/controller/ArticleE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/article/controller/ArticleE2ETest.java @@ -9,11 +9,9 @@ import com.bang_ggood.global.exception.dto.ExceptionResponse; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import io.restassured.http.Header; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; import static org.assertj.core.api.Assertions.assertThat; @@ -27,7 +25,7 @@ public class ArticleE2ETest extends AcceptanceTest { void createArticle() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ArticleFixture.ARTICLE_CREATE_REQUEST()) .when().post("/articles") .then().log().all() @@ -56,7 +54,7 @@ void createArticle_titleBlank_exception() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(request) .when().post("/articles") .then().log().all() @@ -106,7 +104,7 @@ void deleteArticle() { Article article = articleRepository.save(ArticleFixture.ARTICLE()); RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().delete("/articles/" + article.getId()) .then().log().all() .statusCode(204); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/article/service/ArticleServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/article/service/ArticleServiceTest.java index 00a22d3f7..dd8122362 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/article/service/ArticleServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/article/service/ArticleServiceTest.java @@ -76,7 +76,8 @@ void readArticles() { .toList(); // then - assertThat(articleTitles).containsExactly(article4.getTitle(), article3.getTitle(), article2.getTitle(), article1.getTitle()); + assertThat(articleTitles).containsExactly(article4.getTitle(), article3.getTitle(), article2.getTitle(), + article1.getTitle()); } @DisplayName("아티클 삭제 성공") diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/AuthFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/AuthFixture.java new file mode 100644 index 000000000..f34733060 --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/AuthFixture.java @@ -0,0 +1,50 @@ +package com.bang_ggood.auth; + +import com.bang_ggood.auth.dto.request.LocalLoginRequestV1; +import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.service.oauth.OauthRequestProperties; + +public class AuthFixture { + + public static final LocalLoginRequestV1 LOCAL_LOGIN_REQUEST = new LocalLoginRequestV1( + "bang-ggood@gmail.com", + "password1234" + ); + + public static final LocalLoginRequestV1 LOCAL_LOGIN_REQUEST_NO_EMAIL = new LocalLoginRequestV1( + null, + "password1234" + ); + + public static final LocalLoginRequestV1 LOCAL_LOGIN_REQUEST_NO_PASSWORD = new LocalLoginRequestV1( + "bang-ggood@gmail.com", + null + ); + + public static final LocalLoginRequestV1 LOCAL_LOGIN_REQUEST_INVALID_EMAIL = new LocalLoginRequestV1( + "bang-bad@gmail.com", + "password1234" + ); + + public static final LocalLoginRequestV1 LOCAL_LOGIN_REQUEST_INVALID_PASSWORD = new LocalLoginRequestV1( + "bang-ggood@gmail.com", + "password12345" + ); + + public static final OauthLoginRequest OAUTH_LOGIN_REQUEST = new OauthLoginRequest("testCode", "localhost:3000"); + public static final String REGISTERED_REDIRECT_URIS = "localhost:3000, localhost:3001"; + public static final String INVALID_REGISTERED_REDIRECT_URI = "localhost:8081"; + + public static final OauthRequestProperties OAUTH_REQUEST_PROPERTIES() { + String tokenPostUri = "testTokenPostUri"; + String userInfoRequestUri = "testUserInfoRequestUri"; + String grantType = "testGrantType"; + String clientId = "testClientId"; + String clientSecret = "testClientSecret"; + + return new OauthRequestProperties( + tokenPostUri, userInfoRequestUri, + grantType, clientId, + REGISTERED_REDIRECT_URIS, clientSecret); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/JwtTokenProviderFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/JwtTokenFixture.java similarity index 52% rename from backend/bang-ggood/src/test/java/com/bang_ggood/auth/JwtTokenProviderFixture.java rename to backend/bang-ggood/src/test/java/com/bang_ggood/auth/JwtTokenFixture.java index 33ebe6c1d..2ed12dbc7 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/JwtTokenProviderFixture.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/JwtTokenFixture.java @@ -1,20 +1,20 @@ package com.bang_ggood.auth; -import com.bang_ggood.auth.service.JwtTokenProperties; -import com.bang_ggood.auth.service.JwtTokenProvider; +import com.bang_ggood.auth.service.jwt.JwtTokenProperties; +import com.bang_ggood.auth.service.jwt.JwtTokenProvider; import java.security.SecureRandom; import java.util.Base64; -public class JwtTokenProviderFixture { +public class JwtTokenFixture { private static final long THIRTY_MINUTE = 1800000L; public static JwtTokenProvider JWT_TOKEN_PROVIDER_WITH_INVALID_KEY() { - return new JwtTokenProvider(createInvalidJwtSecretKey(), PROPERTIES()); + return new JwtTokenProvider(PROPERTIES_WITH_INVALID_SECRET_KEY()); } public static JwtTokenProvider JWT_TOKEN_PROVIDER_WITH_INVALID_EXPIRED_TIME() { - return new JwtTokenProvider(createJwtSecretKey(), PROPERTIES_WITH_SHORT_EXPIRED_MILLIS()); + return new JwtTokenProvider(PROPERTIES_WITH_SHORT_EXPIRED_MILLIS()); } private static String createJwtSecretKey() { @@ -24,15 +24,11 @@ private static String createJwtSecretKey() { return Base64.getEncoder().encodeToString(key); } - private static String createInvalidJwtSecretKey() { - return "A".repeat(32); - } - private static JwtTokenProperties PROPERTIES_WITH_SHORT_EXPIRED_MILLIS() { - return new JwtTokenProperties(1L, 1L); + return new JwtTokenProperties(createJwtSecretKey(), 1L, 1L); } - private static JwtTokenProperties PROPERTIES() { - return new JwtTokenProperties(THIRTY_MINUTE , THIRTY_MINUTE); + private static JwtTokenProperties PROPERTIES_WITH_INVALID_SECRET_KEY() { + return new JwtTokenProperties(createJwtSecretKey(), THIRTY_MINUTE, THIRTY_MINUTE); } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/config/ArgumentResolverTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/config/ArgumentResolverTest.java index 05ed2de28..7448f42ab 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/config/ArgumentResolverTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/config/ArgumentResolverTest.java @@ -1,6 +1,7 @@ package com.bang_ggood.auth.config; import com.bang_ggood.AcceptanceTest; +import com.bang_ggood.auth.controller.cookie.CookieProvider; import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.user.UserFixture; import com.bang_ggood.user.domain.User; @@ -20,6 +21,8 @@ class ArgumentResolverTest extends AcceptanceTest { + @Autowired + private CookieProvider cookieProvider; @Autowired private UserRepository userRepository; @@ -27,7 +30,7 @@ class ArgumentResolverTest extends AcceptanceTest { @Test void resolveUserPrincipalArgument_returnGuestUser() { // given & when - userRepository.save(UserFixture.GUEST_USER()); + userRepository.save(UserFixture.GUEST_USER1()); User user = RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -37,7 +40,7 @@ void resolveUserPrincipalArgument_returnGuestUser() { .extract().as(User.class); // then - Assertions.assertThat(user.getType()).isEqualTo(UserType.GUEST); + Assertions.assertThat(user.getUserType()).isEqualTo(UserType.GUEST); } @DisplayName("@UserPrincipal 어노테이션 동작 성공 : 토큰값이 있으면 인증된 유저를 할당한다.") @@ -46,14 +49,14 @@ void resolveUserPrincipalArgument_returnUser() { // given & when User user = RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get(TestController.USER_PRINCIPAL_URL) .then().log().all() .statusCode(200) .extract().as(User.class); // then - Assertions.assertThat(user.getType()).isEqualTo(UserType.USER); + Assertions.assertThat(user.getUserType()).isEqualTo(UserType.USER); } @DisplayName("@AuthPrincipal 어노테이션 동작 성공 : 쿠키값이 없으면 예외를 발생시킨다.") @@ -82,4 +85,34 @@ void resolveAuthPrincipalArgument_throwException_whenTokenEmpty() { .statusCode(401) .body("message", containsString(ExceptionCode.AUTHENTICATION_TOKEN_EMPTY.getMessage())); } + + @DisplayName("@AuthPrinciapl 어노테이션 동작 성공 : 액세스 토큰 존재 X, 리프레시 토큰 존재 O 일때 예외를 발생시킨다.") + @Test + void resolveAuthPrincipalArgument_throwException_whenAccessTokenEmpty() { + // given & when & then + ResponseCookie refreshTokenCookie = cookieProvider.createRefreshTokenCookie("testToken"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header(new Header(HttpHeaders.COOKIE, refreshTokenCookie.toString())) + .when().get(TestController.AUTH_PRINCIPAL_URL) + .then().log().all() + .statusCode(401) + .body("message", containsString(ExceptionCode.AUTHENTICATION_ACCESS_TOKEN_EMPTY.getMessage())); + } + + @DisplayName("@AuthPrinciapl 어노테이션 동작 성공 : 액세스 토큰 존재 O, 리프레시 토큰 존재 X 일때 예외를 발생시킨다.") + @Test + void resolveAuthPrincipalArgument_throwException_whenRefreshTokenEmpty() { + // given & when & then + ResponseCookie accessTokenCookie = cookieProvider.createAccessTokenCookie("testToken"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .header(new Header(HttpHeaders.COOKIE, accessTokenCookie.toString())) + .when().get(TestController.AUTH_PRINCIPAL_URL) + .then().log().all() + .statusCode(401) + .body("message", containsString(ExceptionCode.AUTHENTICATION_REFRESH_TOKEN_EMPTY.getMessage())); + } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/AuthE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/AuthE2ETest.java index 0a6170a95..62f38fdbe 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/AuthE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/AuthE2ETest.java @@ -1,18 +1,23 @@ package com.bang_ggood.auth.controller; import com.bang_ggood.AcceptanceTest; -import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.dto.response.TokenExistResponse; import com.bang_ggood.auth.service.AuthService; import com.bang_ggood.checklist.ChecklistFixture; import com.bang_ggood.global.exception.ExceptionCode; import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.http.Header; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; +import static com.bang_ggood.auth.AuthFixture.LOCAL_LOGIN_REQUEST; +import static com.bang_ggood.auth.AuthFixture.LOCAL_LOGIN_REQUEST_NO_EMAIL; +import static com.bang_ggood.auth.AuthFixture.LOCAL_LOGIN_REQUEST_NO_PASSWORD; +import static com.bang_ggood.auth.AuthFixture.OAUTH_LOGIN_REQUEST; import static org.hamcrest.Matchers.containsString; class AuthE2ETest extends AcceptanceTest { @@ -20,17 +25,63 @@ class AuthE2ETest extends AcceptanceTest { @Autowired private AuthService authService; - @DisplayName("로그인 실패 : 인가코드가 없는 경우") + @DisplayName("로컬 로그인 성공") + @Test + void localLogin() { + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(LOCAL_LOGIN_REQUEST) + .when().post("/v1/local-auth/login") + .then().log().all() + .statusCode(200); + } + + @DisplayName("로컬 로그인 실패: 이메일이 없는 경우") + @Test + void localLogin_noEmail_exception() { + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(LOCAL_LOGIN_REQUEST_NO_EMAIL) + .when().post("/v1/local-auth/login") + .then().log().all() + .statusCode(400) + .body("message", containsString("이메일이 존재하지 않습니다.")); + } + + @DisplayName("로컬 로그인 실패: 비밀번호가 없는 경우") + @Test + void localLogin_noPassword_exception() { + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(LOCAL_LOGIN_REQUEST_NO_PASSWORD) + .when().post("/v1/local-auth/login") + .then().log().all() + .statusCode(400) + .body("message", containsString("비밀번호가 존재하지 않습니다.")); + } + + @DisplayName("카카오 로그인 실패 : 인가코드가 없는 경우") @Test void login_code_notBlank_exception() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .body(new OauthLoginRequest("")) + .body(OAUTH_LOGIN_REQUEST) .when().post("/oauth/login") .then().log().all() .statusCode(400); } + @DisplayName("회원 탈퇴 성공") + @Test + void withdraw() { + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .headers(this.headers) + .when().delete("v1/withdraw") + .then().log().all() + .statusCode(204); + } + @DisplayName("인증 실패 : 쿠키가 없는 경우") @Test void authentication_no_cookie_exception() { @@ -57,4 +108,34 @@ void authentication_invalid_cookie_exception() { .statusCode(401) .body("message", containsString(ExceptionCode.AUTHENTICATION_TOKEN_EMPTY.getMessage())); } + + @DisplayName("토큰 존재여부 반환 성공 : 쿠키가 존재하지 않는 경우") + @Test + void checkTokenExist_returnFalse() { + TokenExistResponse tokenExistResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when().get("/token-exist") + .then().log().all() + .statusCode(200) + .extract() + .as(TokenExistResponse.class); + + Assertions.assertThat(tokenExistResponse.isRefreshTokenExist()).isFalse(); + } + + @DisplayName("토큰 존재여부 반환 성공 : 액세스 토큰이 존재하고 리프레시 토큰이 존재하는 경우") + @Test + void checkTokenExist_AccessTokenExist_RefreshTokenExist() { + TokenExistResponse tokenExistResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .headers(this.headers) + .when().get("/token-exist") + .then().log().all() + .statusCode(200) + .extract() + .as(TokenExistResponse.class); + + Assertions.assertThat(tokenExistResponse.isAccessTokenExist()).isTrue(); + Assertions.assertThat(tokenExistResponse.isRefreshTokenExist()).isTrue(); + } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/CookieResolverTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/cookie/CookieResolverTest.java similarity index 54% rename from backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/CookieResolverTest.java rename to backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/cookie/CookieResolverTest.java index 2e07d4a2e..c22c57d24 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/CookieResolverTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/cookie/CookieResolverTest.java @@ -1,24 +1,30 @@ -package com.bang_ggood.auth.controller; +package com.bang_ggood.auth.controller.cookie; import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + class CookieResolverTest { @DisplayName("쿠키에서 토큰 값 조회 성공 : 값이 액세스 토큰일 때") @Test void extractAccessToken() { // given + HttpServletRequest request = mock(HttpServletRequest.class); String expectedToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2IiwiaWF0Ijox"; CookieResolver cookieResolver = new CookieResolver(); - Cookie[] cookies = { new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, expectedToken) }; + Cookie[] cookies = {new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, expectedToken)}; // when - String token = cookieResolver.extractAccessToken(cookies); + when(request.getCookies()).thenReturn(cookies); + String token = cookieResolver.extractAccessToken(request); // then Assertions.assertThat(token).isEqualTo(expectedToken); @@ -28,29 +34,33 @@ void extractAccessToken() { @Test void extractRefreshToken() { // given + HttpServletRequest request = mock(HttpServletRequest.class); String expectedToken = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2IiwiaWF0Ijox"; CookieResolver cookieResolver = new CookieResolver(); - Cookie[] cookies = { new Cookie(CookieProvider.REFRESH_TOKEN_COOKIE_NAME, expectedToken) }; + Cookie[] cookies = {new Cookie(CookieProvider.REFRESH_TOKEN_COOKIE_NAME, expectedToken)}; // when - String token = cookieResolver.extractRefreshToken(cookies); + when(request.getCookies()).thenReturn(cookies); + String token = cookieResolver.extractRefreshToken(request); // then Assertions.assertThat(token).isEqualTo(expectedToken); } - @DisplayName("쿠키에서 토큰 값 조회 실패 : 토큰 값이 존재하지 않을 때") + @DisplayName("쿠키에서 토큰 값 조회 실패 : 액세스 토큰 값이 존재하지 않을 때") @Test void tokenValueNotExist() { // given + HttpServletRequest request = mock(HttpServletRequest.class); CookieResolver cookieResolver = new CookieResolver(); Cookie[] cookies = new Cookie[1]; cookies[0] = new Cookie("testName", "testValue"); // when & then - Assertions.assertThatThrownBy(() -> cookieResolver.extractAccessToken(cookies)) + when(request.getCookies()).thenReturn(cookies); + Assertions.assertThatThrownBy(() -> cookieResolver.extractAccessToken(request)) .isInstanceOf(BangggoodException.class) - .hasMessage(ExceptionCode.AUTHENTICATION_REQUIRED_TOKEN_EMPTY.getMessage()); + .hasMessage(ExceptionCode.AUTHENTICATION_ACCESS_TOKEN_EMPTY.getMessage()); } @@ -59,11 +69,13 @@ void tokenValueNotExist() { void isAllTokenNotExist_returnFalse() { // given CookieResolver cookieResolver = new CookieResolver(); - Cookie[] cookies = { new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, "test"), + HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); + Cookie[] cookies = {new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, "test"), new Cookie(CookieProvider.REFRESH_TOKEN_COOKIE_NAME, "test")}; // when - boolean result = cookieResolver.isTokenNotExist(cookies); + when(httpServletRequest.getCookies()).thenReturn(cookies); + boolean result = cookieResolver.isTokenEmpty(httpServletRequest); // then Assertions.assertThat(result).isFalse(); @@ -71,13 +83,16 @@ void isAllTokenNotExist_returnFalse() { @DisplayName("쿠키 존재 여부 반환 성공 : 액세스 & 리프레시 토큰 정보가 존재하지 않으면 true를 반환한다.") @Test - void isTokenNotExist_returnTrue() { + void isTokenEmpty_returnTrue() { // given CookieResolver cookieResolver = new CookieResolver(); - Cookie[] cookies = { new Cookie("test", "test") }; + HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); + Cookie[] cookies = {new Cookie("test", "test"), + new Cookie("test", "test")}; // when - boolean result = cookieResolver.isTokenNotExist(cookies); + when(httpServletRequest.getCookies()).thenReturn(cookies); + boolean result = cookieResolver.isTokenEmpty(httpServletRequest); // then Assertions.assertThat(result).isTrue(); @@ -85,13 +100,15 @@ void isTokenNotExist_returnTrue() { @DisplayName("쿠키 존재 여부 반환 성공 : 토큰 정보가 하나라도 존재하지 않으면 false를 반환한다.") @Test - void isTokenNotExist_returnFalse() { + void isTokenNotEmpty_returnFalse() { // given CookieResolver cookieResolver = new CookieResolver(); - Cookie[] cookies = { new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, "test")}; + HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); + Cookie[] cookies = {new Cookie(CookieProvider.ACCESS_TOKEN_COOKIE_NAME, "test")}; // when - boolean result = cookieResolver.isTokenNotExist(cookies); + when(httpServletRequest.getCookies()).thenReturn(cookies); + boolean result = cookieResolver.isTokenEmpty(httpServletRequest); // then Assertions.assertThat(result).isFalse(); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/LoginMockE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/cookie/LoginMockE2ETest.java similarity index 67% rename from backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/LoginMockE2ETest.java rename to backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/cookie/LoginMockE2ETest.java index 3c62af379..175cf2463 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/LoginMockE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/controller/cookie/LoginMockE2ETest.java @@ -1,42 +1,49 @@ -package com.bang_ggood.auth.controller; +package com.bang_ggood.auth.controller.cookie; import com.bang_ggood.AcceptanceMockTestSupport; import com.bang_ggood.auth.dto.request.OauthLoginRequest; import com.bang_ggood.auth.dto.response.AuthTokenResponse; import com.bang_ggood.auth.service.AuthService; +import com.fasterxml.jackson.databind.ObjectMapper; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import static com.bang_ggood.auth.AuthFixture.OAUTH_LOGIN_REQUEST; import static org.mockito.ArgumentMatchers.any; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class LoginMockE2ETest extends AcceptanceMockTestSupport { + private static final String COOKIE_DELIMITER = "="; + @Autowired + ObjectMapper objectMapper; @MockBean AuthService authService; - private static final String COOKIE_DELIMITER = "="; - @DisplayName("로그인 성공 : 액세스 토큰과 리프레시 토큰을 쿠키로 반환한다.") @Test void login() throws Exception { // given AuthTokenResponse authTokenResponse = AuthTokenResponse.of("accessToken", "refreshToken"); - String accessTokenCookieHeader = CookieProvider.ACCESS_TOKEN_COOKIE_NAME + COOKIE_DELIMITER + authTokenResponse.accessToken(); - String refreshTokenCookieHeader = CookieProvider.REFRESH_TOKEN_COOKIE_NAME + COOKIE_DELIMITER + authTokenResponse.refreshToken(); + String accessTokenCookieHeader = + CookieProvider.ACCESS_TOKEN_COOKIE_NAME + COOKIE_DELIMITER + authTokenResponse.accessToken(); + String refreshTokenCookieHeader = + CookieProvider.REFRESH_TOKEN_COOKIE_NAME + COOKIE_DELIMITER + authTokenResponse.refreshToken(); + String oauthLoginRequestJson = objectMapper.writeValueAsString(OAUTH_LOGIN_REQUEST); // when & then - Mockito.when(authService.login(any(OauthLoginRequest.class))).thenReturn(authTokenResponse); + Mockito.when(authService.oauthLogin(any(OauthLoginRequest.class))).thenReturn(authTokenResponse); mockMvc.perform(post("/oauth/login") .contentType(MediaType.APPLICATION_JSON) - .content("{\"code\":\"code\"}")) // 요청 본문 + .content(oauthLoginRequestJson)) .andExpect(status().isOk()) .andExpect(result -> { String[] cookies = result.getResponse().getHeaders(HttpHeaders.SET_COOKIE).toArray(new String[0]); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java index 628087caf..8b725a38d 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java @@ -1,9 +1,11 @@ package com.bang_ggood.auth.service; import com.bang_ggood.IntegrationTestSupport; -import com.bang_ggood.auth.controller.CookieProvider; import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.dto.request.RegisterRequestV1; import com.bang_ggood.auth.dto.response.AuthTokenResponse; +import com.bang_ggood.auth.service.jwt.JwtTokenProvider; +import com.bang_ggood.auth.service.oauth.OauthClient; import com.bang_ggood.checklist.dto.response.ChecklistsPreviewResponse; import com.bang_ggood.checklist.service.ChecklistManageService; import com.bang_ggood.global.exception.BangggoodException; @@ -15,6 +17,7 @@ import com.bang_ggood.user.UserFixture; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.repository.UserRepository; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -22,7 +25,14 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.expression.spel.support.ReflectivePropertyAccessor.OptimalPropertyAccessor; +import java.util.Optional; + +import static com.bang_ggood.auth.AuthFixture.LOCAL_LOGIN_REQUEST; +import static com.bang_ggood.auth.AuthFixture.LOCAL_LOGIN_REQUEST_INVALID_EMAIL; +import static com.bang_ggood.auth.AuthFixture.LOCAL_LOGIN_REQUEST_INVALID_PASSWORD; +import static com.bang_ggood.auth.AuthFixture.OAUTH_LOGIN_REQUEST; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; @@ -31,7 +41,6 @@ @ExtendWith(MockitoExtension.class) class AuthServiceTest extends IntegrationTestSupport { - private static final OauthLoginRequest oauthLoginRequest = new OauthLoginRequest("testCode"); @MockBean private OauthClient oauthClient; @Autowired @@ -45,46 +54,139 @@ class AuthServiceTest extends IntegrationTestSupport { @Autowired private JwtTokenProvider jwtTokenProvider; - @DisplayName("로그인 성공 : 존재하지 않는 회원이면 데이터베이스에 새로운 유저를 추가하고 토큰을 반환한다.") + @DisplayName("로컬 로그인 성공") + @Test + void localLogin() { + // given & when + AuthTokenResponse response = authService.localLogin(LOCAL_LOGIN_REQUEST); + + // then + assertThat(response.accessToken()).isNotBlank(); + assertThat(response.refreshToken()).isNotBlank(); + } + + @DisplayName("로컬 로그인 실패: 일치하는 유저가 없는 경우") + @Test + void localLogin_userNotFound() { + // given & when & then + assertThatThrownBy(() -> authService.localLogin(LOCAL_LOGIN_REQUEST_INVALID_EMAIL)) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.USER_NOT_FOUND.getMessage()); + } + + @DisplayName("로컬 로그인 실패: 비밀번호가 일치하지 않는 경우") + @Test + void localLogin_userInvalidPassword() { + // given & when & then + assertThatThrownBy(() -> authService.localLogin(LOCAL_LOGIN_REQUEST_INVALID_PASSWORD)) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.USER_INVALID_PASSWORD.getMessage()); + } + + @DisplayName("회원가입 성공") + @Test + void register() { + //given + RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", "password1234"); + + //when + Long userId = authService.register(request); + + //then + User findUser = userRepository.findById(userId).orElseThrow(); + assertThat(findUser.getId()).isEqualTo(userId); + } + + @DisplayName("회원가입 성공 : 비밀번호 암호화") + @Test + void register_encodePassword() { + // given + String password = "password1234"; + RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", password); + + // when + Long userId = authService.register(request); + User findUser = userRepository.findById(userId).orElseThrow(); + String findPassword = findUser.getPassword().getValue(); + + String[] passwordParts = findPassword.split(":"); + String salt = passwordParts[1]; + + String expectedPassword = PasswordEncoder.encodeWithSpecificSalt(password, findPassword); + + // then + assertThat(findPassword).isEqualTo(expectedPassword); + } + + @DisplayName("회원가입 실패 : 이미 사용되는 이메일인 경우") + @Test + void register_emailAlreadyUsed() { + //given + RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", "password1234"); + + //when + authService.register(request); + + //then + assertThatThrownBy(() -> authService.register(request)) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.USER_EMAIL_ALREADY_USED.getMessage()); + } + + @DisplayName("회원 탈퇴 성공") + @Test + void withdraw() { + //given + userRepository.save(UserFixture.USER1()); + + //when + authService.withdraw(UserFixture.USER1_WITH_ID()); + + //then + Optional findUser = userRepository.findById(UserFixture.USER1_WITH_ID().getId()); + assertThat(findUser).isEmpty(); + } + + @DisplayName("카카오 로그인 성공 : 존재하지 않는 회원이면 데이터베이스에 새로운 유저를 추가하고 토큰 반환") @Test - void login_signup() { + void oauthLogin_signup() { // given Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class))) .thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER2()); // when - AuthTokenResponse token = authService.login(oauthLoginRequest); + AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // then assertThat(token.accessToken()).isNotBlank(); assertThat(token.refreshToken()).isNotBlank(); } - @DisplayName("로그인 성공 : 존재하는 회원이면 데이터베이스에 새로운 유저를 추가하지않고 토큰을 바로 반환한다.") + @DisplayName("카카오 로그인 성공 : 존재하는 회원이면 데이터베이스에 새로운 유저를 추가하지 않고 토큰을 바로 반환") @Test - void login() { + void oauthLogin() { // given userRepository.save(UserFixture.USER1()); Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class))) .thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER1()); // when - AuthTokenResponse token = authService.login(oauthLoginRequest); + AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // then assertThat(token.accessToken()).isNotBlank(); assertThat(token.refreshToken()).isNotBlank(); } - @DisplayName("로그인 성공 : 회원 가입시 디폴트 체크리스트 질문을 추가한다.") + @DisplayName("카카오 로그인 성공 : 회원 가입시 디폴트 체크리스트 질문을 추가") @Test - void login_default_checklist_question() { + void oauthLogin_default_checklist_question() { // given Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class))) .thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER2()); // when - AuthTokenResponse token = authService.login(oauthLoginRequest); + AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // then User user = authService.getAuthUser(token.accessToken()); @@ -99,15 +201,15 @@ void login_default_checklist_question() { assertThat(sum).isEqualTo(Question.findDefaultQuestions().size()); } - @DisplayName("로그인 성공 : 회원 가입시 디폴트 체크리스트를 추가한다.") + @DisplayName("카카오 로그인 성공 : 회원 가입시 디폴트 체크리스트를 추가") @Test - void login_default_checklist() { + void oauthLogin_default_checklist() { // given Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class))) .thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER2()); // when - AuthTokenResponse token = authService.login(oauthLoginRequest); + AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // then User user = authService.getAuthUser(token.accessToken()); @@ -115,12 +217,12 @@ void login_default_checklist() { assertThat(response.checklists()).hasSize(1); } - @DisplayName("게스트 유저 할당 실패 : 게스트 유저의 수가 2명이면 예외를 발생시킨다.") + @DisplayName("게스트 유저 할당 실패 : 게스트 유저의 수가 2명이면 예외를 발생") @Test void assignGuestUser_UnexpectedGuestUserExist() { // given - userRepository.save(UserFixture.GUEST_USER()); - userRepository.save(UserFixture.GUEST_USER()); + userRepository.save(UserFixture.GUEST_USER1()); + userRepository.save(UserFixture.GUEST_USER2()); // when & then assertThatThrownBy(() -> authService.assignGuestUser()) @@ -128,7 +230,7 @@ void assignGuestUser_UnexpectedGuestUserExist() { .hasMessage(ExceptionCode.GUEST_USER_UNEXPECTED_EXIST.getMessage()); } - @DisplayName("게스트 유저 할당 실패 : 게스트 유저가 존재하지 않으면 예외를 발생시킨다.") + @DisplayName("게스트 유저 할당 실패 : 게스트 유저가 존재하지 않으면 예외를 발생") @Test void assingGuestUser_GuestUserNotExist() { // when & then @@ -141,7 +243,7 @@ void assingGuestUser_GuestUserNotExist() { @Test void assignGuestUser() { // given - User guestUser = userRepository.save(UserFixture.GUEST_USER()); + User guestUser = userRepository.save(UserFixture.GUEST_USER1()); // when User assignedGuestUser = authService.assignGuestUser(); @@ -168,15 +270,15 @@ void logout_invalid_ownership_exception() { @DisplayName("액세스 토큰 재발행 성공") @Test - void reIssueAccessToken() { + void reissueAccessToken() { // given userRepository.save(UserFixture.USER1()); Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class))) .thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER1()); - AuthTokenResponse tokenResponse = authService.login(oauthLoginRequest); + AuthTokenResponse tokenResponse = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // when & then - assertThatCode(() -> authService.reIssueAccessToken(tokenResponse.refreshToken())) + assertThatCode(() -> authService.reissueAccessToken(tokenResponse.refreshToken())) .doesNotThrowAnyException(); } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/JwtTokenProviderTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/JwtTokenProviderTest.java index 09ca606bb..86abb435e 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/JwtTokenProviderTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/JwtTokenProviderTest.java @@ -1,7 +1,9 @@ package com.bang_ggood.auth.service; import com.bang_ggood.IntegrationTestSupport; -import com.bang_ggood.auth.JwtTokenProviderFixture; +import com.bang_ggood.auth.JwtTokenFixture; +import com.bang_ggood.auth.service.jwt.JwtTokenProvider; +import com.bang_ggood.auth.service.jwt.JwtTokenResolver; import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.user.UserFixture; @@ -14,6 +16,8 @@ class JwtTokenProviderTest extends IntegrationTestSupport { + @Autowired + private JwtTokenResolver jwtTokenResolver; @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired @@ -27,38 +31,23 @@ void createToken() { String token = jwtTokenProvider.createAccessToken(user); // when - AuthUser authUser = jwtTokenProvider.resolveToken(token); + AuthUser authUser = jwtTokenResolver.resolveAccessToken(token); // then Assertions.assertThat(authUser.id()).isEqualTo(user.getId()); } - @DisplayName("토큰 획인 실패 : 유효시간이 지난 경우") - @Test - void resolveToken_expiredTime_exception() { - // given - JwtTokenProvider expiredJwtTokenProvider = JwtTokenProviderFixture.JWT_TOKEN_PROVIDER_WITH_INVALID_EXPIRED_TIME(); - - User user = userRepository.save(UserFixture.USER1()); - String token = expiredJwtTokenProvider.createAccessToken(user); - - // when & then - Assertions.assertThatCode(() -> expiredJwtTokenProvider.resolveToken(token)) - .isInstanceOf(BangggoodException.class) - .hasMessage(ExceptionCode.AUTHENTICATION_TOKEN_EXPIRED.getMessage()); - } - @DisplayName("토큰 확인 실패 : 시그니처가 잘못된 경우") @Test void resolveToken_invalidSignature_exception() { // given - JwtTokenProvider invalidJwtTokenProvider = JwtTokenProviderFixture.JWT_TOKEN_PROVIDER_WITH_INVALID_KEY(); + JwtTokenProvider invalidJwtTokenProvider = JwtTokenFixture.JWT_TOKEN_PROVIDER_WITH_INVALID_KEY(); User user = userRepository.save(UserFixture.USER1()); - String token = jwtTokenProvider.createAccessToken(user); + String token = invalidJwtTokenProvider.createAccessToken(user); // when & then - Assertions.assertThatCode(() -> invalidJwtTokenProvider.resolveToken(token)) + Assertions.assertThatCode(() -> jwtTokenResolver.resolveAccessToken(token)) .isInstanceOf(BangggoodException.class) .hasMessage(ExceptionCode.AUTHENTICATION_TOKEN_INVALID.getMessage()); } @@ -70,7 +59,7 @@ void resolveToken_invalidToken_exception() { String invalidToken = "malformed"; // when & then - Assertions.assertThatCode(() -> jwtTokenProvider.resolveToken(invalidToken)) + Assertions.assertThatCode(() -> jwtTokenResolver.resolveAccessToken(invalidToken)) .isInstanceOf(BangggoodException.class) .hasMessage(ExceptionCode.AUTHENTICATION_TOKEN_INVALID.getMessage()); } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/OauthPropertiesTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/OauthPropertiesTest.java new file mode 100644 index 000000000..c2cf69b0b --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/OauthPropertiesTest.java @@ -0,0 +1,59 @@ +package com.bang_ggood.auth.service; + +import com.bang_ggood.IntegrationTestSupport; +import com.bang_ggood.auth.AuthFixture; +import com.bang_ggood.auth.dto.request.OauthLoginRequest; +import com.bang_ggood.auth.service.oauth.OauthRequestProperties; +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OauthPropertiesTest extends IntegrationTestSupport { + + @DisplayName("토큰 요청 바디 생성 실패 : Redirect Uri이 일치하지 않을 때") + @Test + void createTokenRequestBodyFail_whenRedirectUriMisMatch() { + // given + OauthRequestProperties oauthRequestProperties = AuthFixture.OAUTH_REQUEST_PROPERTIES(); + + String invalidRedirectUri = AuthFixture.INVALID_REGISTERED_REDIRECT_URI; + OauthLoginRequest oauthLoginRequest = new OauthLoginRequest("testCode", invalidRedirectUri); + + // when & then + Assertions.assertThatThrownBy(() -> oauthRequestProperties.createTokenRequestBody(oauthLoginRequest)) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.OAUTH_REDIRECT_URI_MISMATCH.getMessage()); + } + + @DisplayName("토큰 요청 바디 생성 성공") + @Test + void createTokenRequestBody() { + // given + OauthRequestProperties oauthRequestProperties = AuthFixture.OAUTH_REQUEST_PROPERTIES(); + String redirectUris = AuthFixture.REGISTERED_REDIRECT_URIS; + OauthLoginRequest oauthLoginRequest = new OauthLoginRequest("testCode", "localhost:3000"); + + // when & then + Assertions.assertThatCode(() -> oauthRequestProperties.createTokenRequestBody(oauthLoginRequest)) + .doesNotThrowAnyException(); + } + + @DisplayName("Redirect Uri 여러개를 받아 저장한다.") + @Test + void convertToList() { + // given + String firstRedirectUri = "localhost:3000"; + String secondRedirectUri = "localhost:3001"; + String testRegisteredUris = firstRedirectUri + ", " + secondRedirectUri; + + OauthRequestProperties oauthRequestProperties = new OauthRequestProperties( + "testPostUri", "testUserUri", "testGrantType", + "testClientId", testRegisteredUris, "testClientSecret"); + + // when & then + Assertions.assertThat(oauthRequestProperties.getRegisteredRedirectUris()) + .containsExactlyInAnyOrder(firstRedirectUri, secondRedirectUri); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/PasswordEncoderTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/PasswordEncoderTest.java new file mode 100644 index 000000000..35cd297df --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/PasswordEncoderTest.java @@ -0,0 +1,34 @@ +package com.bang_ggood.auth.service; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PasswordEncoderTest { + + @DisplayName("고유 salt로 비밀번호 암호화 성공") + @Test + void encodeWithGeneralSalt() { + // given & when + String password = "bang-ggood"; + String encodedPassword = PasswordEncoder.encodeWithGeneralSalt("bang-ggood"); + + // then + assertThat(password).isNotEqualTo(encodedPassword); + } + + @DisplayName("특정 salt로 비밀번호 암호화 성공") + @Test + void encodeWithSpecificSalt() { + // given + String password = "bang-ggood"; + String encodedPassword = PasswordEncoder.encodeWithGeneralSalt(password); + + // when + String targetPassword = PasswordEncoder.encodeWithSpecificSalt(password, encodedPassword); + + // then + assertThat(targetPassword).isEqualTo(encodedPassword); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/ChecklistFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/ChecklistFixture.java index 03a33279a..c4e72bacf 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/ChecklistFixture.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/ChecklistFixture.java @@ -2,18 +2,18 @@ import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.dto.request.ChecklistRequest; +import com.bang_ggood.checklist.dto.request.ChecklistRequestV1; import com.bang_ggood.contract.domain.OccupancyMonth; import com.bang_ggood.contract.domain.OccupancyPeriod; import com.bang_ggood.like.domain.ChecklistLike; import com.bang_ggood.maintenance.domain.ChecklistMaintenance; import com.bang_ggood.maintenance.domain.MaintenanceItem; import com.bang_ggood.option.domain.Option; -import com.bang_ggood.question.domain.Answer; -import com.bang_ggood.question.domain.ChecklistQuestion; import com.bang_ggood.question.domain.Question; import com.bang_ggood.question.dto.request.QuestionRequest; import com.bang_ggood.room.RoomFixture; import com.bang_ggood.room.domain.Room; +import com.bang_ggood.station.dto.request.ChecklistStationRequest; import com.bang_ggood.user.UserFixture; import com.bang_ggood.user.domain.User; import java.util.List; @@ -110,15 +110,30 @@ public static QuestionRequest QUESTION_CREATE_REQUEST_NO_ID() { public static ChecklistRequest CHECKLIST_CREATE_REQUEST() { return new ChecklistRequest( - RoomFixture.ROOM_CREATE_REQUEST(), List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), Option.SHOE_RACK.getId()), + RoomFixture.ROOM_CREATE_REQUEST(), + List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), + Option.SHOE_RACK.getId()), List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), QUESTION_3_CREATE_REQUEST(), QUESTION_5_CREATE_REQUEST()) ); } + public static ChecklistRequestV1 CHECKLIST_CREATE_REQUEST_V1() { + return new ChecklistRequestV1( + RoomFixture.ROOM_CREATE_REQUEST(), + List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), + Option.SHOE_RACK.getId()), + List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), + QUESTION_3_CREATE_REQUEST(), QUESTION_5_CREATE_REQUEST()), + ChecklistStationRequest.of(38, 127) + ); + } + public static ChecklistRequest CHECKLIST_CREATE_REQUEST2() { return new ChecklistRequest( - RoomFixture.ROOM_CREATE_REQUEST(), List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), Option.CLOSET.getId()), + RoomFixture.ROOM_CREATE_REQUEST(), + List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), + Option.CLOSET.getId()), List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), QUESTION_4_CREATE_REQUEST()) ); @@ -126,7 +141,9 @@ public static ChecklistRequest CHECKLIST_CREATE_REQUEST2() { public static ChecklistRequest CHECKLIST_CREATE_REQUEST_NO_ROOM_NAME() { return new ChecklistRequest( - RoomFixture.ROOM_CREATE_REQUEST_NO_ROOM_NAME(),List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), Option.SHOE_RACK.getId()), + RoomFixture.ROOM_CREATE_REQUEST_NO_ROOM_NAME(), + List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), + Option.SHOE_RACK.getId()), List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), QUESTION_3_CREATE_REQUEST(), QUESTION_5_CREATE_REQUEST()) ); @@ -142,7 +159,9 @@ public static ChecklistRequest CHECKLIST_UPDATE_REQUEST() { public static ChecklistRequest CHECKLIST_UPDATE_REQUEST_NO_ROOM_NAME() { return new ChecklistRequest( - RoomFixture.ROOM_CREATE_REQUEST_NO_ROOM_NAME(), List.of(Option.REFRIGERATOR.getId(), Option.BED.getId(), Option.INDUCTION.getId(), Option.SHOE_RACK.getId()), + RoomFixture.ROOM_CREATE_REQUEST_NO_ROOM_NAME(), + List.of(Option.REFRIGERATOR.getId(), Option.BED.getId(), Option.INDUCTION.getId(), + Option.SHOE_RACK.getId()), List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), QUESTION_3_CREATE_REQUEST(), QUESTION_5_UPDATE_REQUEST()) ); @@ -150,7 +169,9 @@ public static ChecklistRequest CHECKLIST_UPDATE_REQUEST_NO_ROOM_NAME() { public static ChecklistRequest CHECKLIST_CREATE_REQUEST_NO_QUESTION_ID() { return new ChecklistRequest( - RoomFixture.ROOM_CREATE_REQUEST(), List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), Option.SHOE_RACK.getId()), + RoomFixture.ROOM_CREATE_REQUEST(), + List.of(Option.REFRIGERATOR.getId(), Option.SINK.getId(), Option.INDUCTION.getId(), + Option.SHOE_RACK.getId()), List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), QUESTION_3_CREATE_REQUEST(), QUESTION_CREATE_REQUEST_NO_ID()) ); @@ -158,20 +179,14 @@ public static ChecklistRequest CHECKLIST_CREATE_REQUEST_NO_QUESTION_ID() { public static ChecklistRequest CHECKLIST_UPDATE_REQUEST_NO_QUESTION_ID() { return new ChecklistRequest( - RoomFixture.ROOM_UPDATE_REQUEST(), List.of(Option.REFRIGERATOR.getId(), Option.BED.getId(), Option.INDUCTION.getId(), Option.SHOE_RACK.getId()), + RoomFixture.ROOM_UPDATE_REQUEST(), + List.of(Option.REFRIGERATOR.getId(), Option.BED.getId(), Option.INDUCTION.getId(), + Option.SHOE_RACK.getId()), List.of(QUESTION_1_CREATE_REQUEST(), QUESTION_2_CREATE_REQUEST(), QUESTION_3_CREATE_REQUEST(), QUESTION_CREATE_REQUEST_NO_ID()) ); } - public static ChecklistQuestion CHECKLIST_QUESTION_1(Checklist checklist) { - return new ChecklistQuestion(checklist, Question.fromId(1), Answer.BAD); - } - - public static ChecklistQuestion CHECKLIST_QUESTION_2(Checklist checklist) { - return new ChecklistQuestion(checklist, Question.fromId(2), Answer.BAD); - } - public static ChecklistLike CHECKLIST1_LIKE(Checklist checklist) { return new ChecklistLike(checklist); } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/controller/ChecklistE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/controller/ChecklistE2ETest.java index 8cd77b784..93dd5367d 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/controller/ChecklistE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/controller/ChecklistE2ETest.java @@ -14,11 +14,9 @@ import com.bang_ggood.room.repository.RoomRepository; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import io.restassured.http.Header; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; import static org.hamcrest.Matchers.containsString; @@ -27,34 +25,42 @@ class ChecklistE2ETest extends AcceptanceTest { @Autowired private ChecklistManageService checklistManageService; @Autowired - private ChecklistService checklistService; - @Autowired private ChecklistRepository checklistRepository; @Autowired private RoomRepository roomRepository; @Autowired private CustomChecklistQuestionRepository customChecklistQuestionRepository; - @Autowired - private ChecklistLikeRepository checklistLikeRepository; @DisplayName("체크리스트 작성 성공") @Test void createChecklist() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ChecklistFixture.CHECKLIST_CREATE_REQUEST()) .when().post("/checklists") .then().log().all() .statusCode(201); } + @DisplayName("체크리스트 작성 v1 성공") + @Test + void createChecklistV1() { + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .headers(this.headers) + .body(ChecklistFixture.CHECKLIST_CREATE_REQUEST_V1()) + .when().post("v1/checklists") + .then().log().all() + .statusCode(201); + } + @DisplayName("체크리스트 작성 실패: 방 이름을 넣지 않은 경우") @Test void createChecklist_noRoomName_exception() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ChecklistFixture.CHECKLIST_CREATE_REQUEST_NO_ROOM_NAME()) .when().post("/checklists") .then().log().all() @@ -67,7 +73,7 @@ void createChecklist_noRoomName_exception() { void createChecklist_noQuestionId_exception() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ChecklistFixture.CHECKLIST_CREATE_REQUEST_NO_QUESTION_ID()) .when().post("/checklists") .then().log().all() @@ -78,13 +84,9 @@ void createChecklist_noQuestionId_exception() { @DisplayName("체크리스트 질문 조회 성공") @Test void readChecklistQuestions() { - // given - customChecklistQuestionRepository.saveAll( - CustomChecklistFixture.CUSTOM_CHECKLIST_QUESTION_DEFAULT(this.getAuthenticatedUser())); - RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get("/checklists/questions") .then().log().all() .statusCode(200); @@ -98,7 +100,7 @@ void readChecklistById() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get("/checklists/" + checklistId) .then().log().all() .statusCode(200); @@ -112,12 +114,26 @@ void readChecklistById() { //TODO 수정 } + @DisplayName("작성된 체크리스트 조회 v1 성공") + @Test + void readChecklistV1() { + long checklistId = checklistManageService.createChecklist(this.getAuthenticatedUser(), + ChecklistFixture.CHECKLIST_CREATE_REQUEST()); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .headers(this.headers) + .when().get("v1/checklists/" + checklistId) + .then().log().all() + .statusCode(200); + } + @DisplayName("좋아요된 체크리스트 리스트 조회 성공") @Test void readLikedUserChecklistsPreview() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get("/checklists/like") .then().log().all() .statusCode(200); @@ -131,7 +147,7 @@ void updateChecklist() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ChecklistFixture.CHECKLIST_UPDATE_REQUEST()) .when().put("/checklists/" + checklistId) .then().log().all() @@ -146,7 +162,7 @@ void updateChecklist_noRoomName_exception() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ChecklistFixture.CHECKLIST_UPDATE_REQUEST_NO_ROOM_NAME()) .when().put("/checklists/" + checklistId) .then().log().all() @@ -162,7 +178,7 @@ void updateChecklist_noQuestionId_exception() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(ChecklistFixture.CHECKLIST_UPDATE_REQUEST_NO_QUESTION_ID()) .when().put("/checklists/" + checklistId) .then().log().all() @@ -179,7 +195,7 @@ void deleteChecklistById() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().delete("/checklists/" + saved.getId()) .then().log().all() .statusCode(204); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/repository/ChecklistRepositoryTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/repository/ChecklistRepositoryTest.java index e1e209bdc..4caf5ce66 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/repository/ChecklistRepositoryTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/repository/ChecklistRepositoryTest.java @@ -21,7 +21,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; class ChecklistRepositoryTest extends IntegrationTestSupport { diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistManageServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistManageServiceTest.java index 854ba0bb3..c66241719 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistManageServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistManageServiceTest.java @@ -4,6 +4,7 @@ import com.bang_ggood.checklist.ChecklistFixture; import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.dto.request.ChecklistRequest; +import com.bang_ggood.checklist.dto.request.ChecklistRequestV1; import com.bang_ggood.checklist.dto.response.ChecklistPreviewResponse; import com.bang_ggood.checklist.dto.response.ChecklistsPreviewResponse; import com.bang_ggood.checklist.dto.response.SelectedChecklistResponse; @@ -22,7 +23,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; - import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -58,6 +58,20 @@ void createChecklist() { assertThat(checklistId).isGreaterThan(0); } + @DisplayName("체크리스트 작성 v1 성공") + @Test + void createChecklistV1() { + //given + User user = userRepository.save(UserFixture.USER1()); + ChecklistRequestV1 checklistRequestV1 = ChecklistFixture.CHECKLIST_CREATE_REQUEST_V1(); + + // when + long checklistId = checklistManageService.createChecklistV1(user, checklistRequestV1); + + //then + assertThat(checklistId).isGreaterThan(0); + } + @DisplayName("작성된 체크리스트 조회 성공") @Test void readChecklist() { diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistServiceTest.java index ea80759a8..632411d47 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/checklist/service/ChecklistServiceTest.java @@ -4,8 +4,6 @@ import com.bang_ggood.checklist.ChecklistFixture; import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.repository.ChecklistRepository; -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.like.repository.ChecklistLikeRepository; import com.bang_ggood.option.repository.ChecklistOptionRepository; import com.bang_ggood.room.RoomFixture; @@ -20,7 +18,6 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/global/datasource/DataSourceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/global/datasource/DataSourceTest.java index fecb40b8f..c0c1b69e3 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/global/datasource/DataSourceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/global/datasource/DataSourceTest.java @@ -35,7 +35,7 @@ void setUp() { @DisplayName("read DB와 연결 성공: read-only인 경우") @Test public void read() { - try (Connection connection = dataSource.getConnection()){ + try (Connection connection = dataSource.getConnection()) { assertThat(connection.getMetaData().getURL()).contains("read"); } catch (SQLException e) { throw new RuntimeException(e); @@ -46,7 +46,7 @@ public void read() { @DisplayName("write DB와 연결 성공: read-only가 아닌 경우") @Test public void write() { - try (Connection connection = dataSource.getConnection()){ + try (Connection connection = dataSource.getConnection()) { assertThat(connection.getMetaData().getURL()).contains("write"); } catch (SQLException e) { throw new RuntimeException(e); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/like/controller/ChecklistLikeE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/like/controller/ChecklistLikeE2ETest.java index 5bc8ae38a..db0cf0683 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/like/controller/ChecklistLikeE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/like/controller/ChecklistLikeE2ETest.java @@ -12,11 +12,9 @@ import com.bang_ggood.room.repository.RoomRepository; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import io.restassured.http.Header; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; class ChecklistLikeE2ETest extends AcceptanceTest { @@ -39,25 +37,25 @@ void createChecklistLike() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().post("/checklists/" + checklistId + "/like") .then().log().all() .statusCode(204); } - @DisplayName("체크리스트 좋아요 추가 실패 : 이미 좋아요가 추가가 된 체크리스트인 경우") + @DisplayName("체크리스트 좋아요 추가 시도 : 이미 좋아요가 추가가 된 체크리스트인 경우") @Test - void createChecklistLike_checklistAlreadyLiked_exception() { + void createChecklistLike_checklistAlreadyLiked() { long checklistId = checklistManageService.createChecklist(getAuthenticatedUser(), ChecklistFixture.CHECKLIST_CREATE_REQUEST()); checklistLikeManageService.createLike(getAuthenticatedUser(), checklistId); RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().post("/checklists/" + checklistId + "/like") .then().log().all() - .statusCode(409); + .statusCode(204); } @DisplayName("체크리스트 좋아요 삭제 성공") @@ -70,7 +68,7 @@ void deleteChecklistLikeByChecklistId() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().delete("/checklists/" + checklist.getId() + "/like") .then().log().all() .statusCode(204); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeManageServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeManageServiceTest.java index 069267039..87901eb2c 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeManageServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeManageServiceTest.java @@ -4,8 +4,6 @@ import com.bang_ggood.checklist.ChecklistFixture; import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.repository.ChecklistRepository; -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.like.domain.ChecklistLike; import com.bang_ggood.like.repository.ChecklistLikeRepository; import com.bang_ggood.room.RoomFixture; @@ -19,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatCode; class ChecklistLikeManageServiceTest extends IntegrationTestSupport { @@ -53,9 +51,9 @@ void createChecklistLike() { assertThat(checklistLikeRepository.existsByChecklist(checklist)).isTrue(); } - @DisplayName("체크리스트 좋아요 추가 실패 : 이미 좋아요가 추가가 된 체크리스트인 경우") + @DisplayName("체크리스트 좋아요 추가 시도 : 이미 좋아요가 추가가 된 체크리스트인 경우") @Test - void createChecklistLike_checklistAlreadyLiked_exception() { + void createChecklistLike_checklistAlreadyLiked() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); @@ -65,9 +63,7 @@ void createChecklistLike_checklistAlreadyLiked_exception() { checklistLikeManageService.createLike(user, checklist.getId()); //then - assertThatThrownBy(() -> checklistLikeManageService.createLike(user, checklist.getId())) - .isInstanceOf(BangggoodException.class) - .hasMessage(ExceptionCode.LIKE_ALREADY_EXISTS.getMessage()); + assertThat(checklistLikeRepository.count()).isEqualTo(1); } @DisplayName("체크리스트 좋아요 삭제 성공") @@ -86,17 +82,16 @@ void deleteChecklistLikeByChecklistId() { assertThat(checklistLikeRepository.existsById(checklistLike.getId())).isFalse(); } - @DisplayName("체크리스트 좋아요 삭제 실패 : 체크리스트 좋아요가 없는 경우") + @DisplayName("체크리스트 좋아요 삭제 시도 : 체크리스트 좋아요가 없는 경우") @Test - void deleteChecklistLikeByChecklistId_notFound_exception() { + void deleteChecklistLikeByChecklistId_notFound() { // given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); // when & then - assertThatThrownBy(() -> checklistLikeManageService.deleteLike(user, checklist.getId())) - .isInstanceOf(BangggoodException.class) - .hasMessage(ExceptionCode.LIKE_NOT_EXISTS.getMessage()); + assertThatCode(() -> checklistLikeManageService.deleteLike(user, checklist.getId())) + .doesNotThrowAnyException(); } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeServiceTest.java index e30c53112..bdeeef3de 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/like/service/ChecklistLikeServiceTest.java @@ -4,8 +4,6 @@ import com.bang_ggood.checklist.ChecklistFixture; import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.repository.ChecklistRepository; -import com.bang_ggood.global.exception.BangggoodException; -import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.like.domain.ChecklistLike; import com.bang_ggood.like.repository.ChecklistLikeRepository; import com.bang_ggood.room.RoomFixture; @@ -19,7 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatCode; class ChecklistLikeServiceTest extends IntegrationTestSupport { @@ -53,20 +51,19 @@ void createChecklistLike() { assertThat(checklistLikeRepository.existsByChecklist(checklist)).isTrue(); } - @DisplayName("체크리스트 좋아요 추가 실패 : 이미 좋아요가 추가가 된 체크리스트인 경우") + @DisplayName("체크리스트 좋아요 추가 시도 : 이미 좋아요가 추가가 된 체크리스트인 경우") @Test - void createChecklistLike_checklistAlreadyLiked_exception() { + void createChecklistLike_checklistAlreadyLiked() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); // when checklistLikeService.createLike(user, checklist); + checklistLikeService.createLike(user, checklist); //then - assertThatThrownBy(() -> checklistLikeService.createLike(user, checklist)) - .isInstanceOf(BangggoodException.class) - .hasMessage(ExceptionCode.LIKE_ALREADY_EXISTS.getMessage()); + assertThat(checklistLikeRepository.count()).isEqualTo(1); } @DisplayName("체크리스트 좋아요 삭제 성공") @@ -85,17 +82,16 @@ void deleteChecklistLikeByChecklistId() { assertThat(checklistLikeRepository.existsById(checklistLike.getId())).isFalse(); } - @DisplayName("체크리스트 좋아요 삭제 실패 : 체크리스트 좋아요가 없는 경우") + @DisplayName("체크리스트 좋아요 삭제 시도 : 체크리스트 좋아요가 없는 경우") @Test - void deleteChecklistLikeByChecklistId_notFound_exception() { + void deleteChecklistLikeByChecklistId_notFound() { // given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); // when & then - assertThatThrownBy(() -> checklistLikeService.deleteLike(user, checklist)) - .isInstanceOf(BangggoodException.class) - .hasMessage(ExceptionCode.LIKE_NOT_EXISTS.getMessage()); + assertThatCode(() -> checklistLikeService.deleteLike(user, checklist)) + .doesNotThrowAnyException(); } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/ChecklistQuestionFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/ChecklistQuestionFixture.java index 2fd0dfe67..7125648d1 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/ChecklistQuestionFixture.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/ChecklistQuestionFixture.java @@ -4,59 +4,64 @@ import com.bang_ggood.question.domain.Answer; import com.bang_ggood.question.domain.ChecklistQuestion; import com.bang_ggood.question.domain.Question; +import com.bang_ggood.question.domain.QuestionEntity; import java.util.List; public class ChecklistQuestionFixture { - public static ChecklistQuestion CHECKLIST1_QUESTION1(Checklist checklist) { + public static ChecklistQuestion CHECKLIST1_QUESTION1(Checklist checklist, QuestionEntity question) { return new ChecklistQuestion( checklist, Question.ROOM_CONDITION_1, + question, Answer.BAD ); } - public static ChecklistQuestion CHECKLIST1_QUESTION10(Checklist checklist) { + public static ChecklistQuestion CHECKLIST1_QUESTION2(Checklist checklist, QuestionEntity question) { return new ChecklistQuestion( checklist, Question.WINDOW_1, + question, Answer.GOOD ); } - public static ChecklistQuestion CHECKLIST1_QUESTION10_UPDATE_ANSWER(Checklist checklist) { + public static ChecklistQuestion CHECKLIST1_QUESTION2_UPDATE_ANSWER(Checklist checklist, QuestionEntity question) { return new ChecklistQuestion( checklist, Question.WINDOW_1, + question, Answer.BAD ); } - public static ChecklistQuestion CHECKLIST1_QUESTION11(Checklist checklist) { + public static ChecklistQuestion CHECKLIST1_QUESTION11(Checklist checklist, QuestionEntity question) { return new ChecklistQuestion( checklist, Question.WINDOW_2, + question, null ); } - public static List CHECKLIST1_QUESTIONS(Checklist checklist) { - return List.of(CHECKLIST1_QUESTION1(checklist), CHECKLIST1_QUESTION10(checklist)); + public static List CHECKLIST1_QUESTIONS(Checklist checklist, QuestionEntity question1, QuestionEntity question2) { + return List.of(CHECKLIST1_QUESTION1(checklist, question1), CHECKLIST1_QUESTION2(checklist, question2)); } - public static List CHECKLIST1_DUPLICATE(Checklist checklist) { - return List.of(CHECKLIST1_QUESTION1(checklist), CHECKLIST1_QUESTION1(checklist)); + public static List CHECKLIST1_DUPLICATE(Checklist checklist, QuestionEntity question) { + return List.of(CHECKLIST1_QUESTION1(checklist, question), CHECKLIST1_QUESTION1(checklist, question)); } - public static List CHECKLIST1_QUESTIONS_UPDATE(Checklist checklist) { - return List.of(CHECKLIST1_QUESTION1(checklist), CHECKLIST1_QUESTION10_UPDATE_ANSWER(checklist)); + public static List CHECKLIST1_QUESTIONS_UPDATE(Checklist checklist, QuestionEntity question1, QuestionEntity question2) { + return List.of(CHECKLIST1_QUESTION1(checklist, question1), CHECKLIST1_QUESTION2_UPDATE_ANSWER(checklist, question2)); } - public static List CHECKLIST1_QUESTIONS_DIFFERENT_LENGTH(Checklist checklist) { - return List.of(CHECKLIST1_QUESTION10_UPDATE_ANSWER(checklist)); + public static List CHECKLIST1_QUESTIONS_DIFFERENT_LENGTH(Checklist checklist, QuestionEntity question) { + return List.of(CHECKLIST1_QUESTION2_UPDATE_ANSWER(checklist, question)); } - public static List CHECKLIST1_QUESTIONS_DIFFERENT_QUESTION(Checklist checklist) { - return List.of(CHECKLIST1_QUESTION10_UPDATE_ANSWER(checklist), CHECKLIST1_QUESTION11(checklist)); + public static List CHECKLIST1_QUESTIONS_DIFFERENT_QUESTION(Checklist checklist, QuestionEntity question2, QuestionEntity question1) { + return List.of(CHECKLIST1_QUESTION2_UPDATE_ANSWER(checklist, question2), CHECKLIST1_QUESTION11(checklist, question1)); } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/CustomChecklistFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/CustomChecklistFixture.java index 02ac51d44..c764a29d4 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/CustomChecklistFixture.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/CustomChecklistFixture.java @@ -3,6 +3,7 @@ import com.bang_ggood.question.domain.CustomChecklistQuestion; import com.bang_ggood.question.domain.Question; import com.bang_ggood.question.dto.request.CustomChecklistUpdateRequest; +import com.bang_ggood.user.UserFixture; import com.bang_ggood.user.domain.User; import java.util.ArrayList; import java.util.List; @@ -10,12 +11,13 @@ public class CustomChecklistFixture { - public static List CUSTOM_CHECKLIST_QUESTION_DEFAULT(User user) { - return List.of(new CustomChecklistQuestion(user, Question.ROOM_CONDITION_1), - new CustomChecklistQuestion(user, Question.WINDOW_1), - new CustomChecklistQuestion(user, Question.BATHROOM_1), - new CustomChecklistQuestion(user, Question.SECURITY_1)); + public static List CUSTOM_CHECKLIST_QUESTION_DEFAULT; + public static CustomChecklistUpdateRequest CUSTOM_CHECKLIST_UPDATE_REQUEST; + + public static List CUSTOM_CHECKLIST_QUESTION_DEFAULT(User user) { + return List.of(new CustomChecklistQuestion(user, Question.ROOM_CONDITION_1, QuestionFixture.QUESTION1_CATEGORY1), + new CustomChecklistQuestion(user, Question.WINDOW_1, QuestionFixture.QUESTION3_CATEGORY2)); } public static CustomChecklistUpdateRequest CUSTOM_CHECKLIST_UPDATE_REQUEST() { @@ -36,4 +38,9 @@ public static CustomChecklistUpdateRequest CUSTOM_CHECKLIST_UPDATE_REQUEST_DUPLI public static CustomChecklistUpdateRequest CUSTOM_CHECKLIST_UPDATE_REQUEST_INVALID() { return new CustomChecklistUpdateRequest(List.of(99999)); } + + public static void init() { + CUSTOM_CHECKLIST_QUESTION_DEFAULT = List.of(new CustomChecklistQuestion(UserFixture.USER1, Question.ROOM_CONDITION_1, QuestionFixture.QUESTION1_CATEGORY1), + new CustomChecklistQuestion(UserFixture.USER1, Question.WINDOW_1, QuestionFixture.QUESTION2_CATEGORY1)); + } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/QuestionFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/QuestionFixture.java new file mode 100644 index 000000000..297ce98e6 --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/QuestionFixture.java @@ -0,0 +1,23 @@ +package com.bang_ggood.question; + +import com.bang_ggood.question.domain.CategoryEntity; +import com.bang_ggood.question.domain.QuestionEntity; +import com.bang_ggood.question.repository.CategoryRepository; +import com.bang_ggood.question.repository.QuestionRepository; + +public class QuestionFixture { + + public static QuestionEntity QUESTION1_CATEGORY1; + public static QuestionEntity QUESTION2_CATEGORY1; + public static QuestionEntity QUESTION3_CATEGORY2; + public static CategoryEntity CATEGORY1; + public static CategoryEntity CATEGORY2; + + public static void init(CategoryRepository categoryRepository, QuestionRepository questionRepository) { + CATEGORY1 = categoryRepository.save(new CategoryEntity("방 컨디션")); + CATEGORY2 = categoryRepository.save(new CategoryEntity("창문")); + QUESTION1_CATEGORY1 = questionRepository.save(new QuestionEntity(CATEGORY1, "testTitle1", "testSubTitle1", true)); + QUESTION2_CATEGORY1 = questionRepository.save(new QuestionEntity(CATEGORY1, "testTitle2", "testSubTitle2", true)); + QUESTION3_CATEGORY2 = questionRepository.save(new QuestionEntity(CATEGORY2, "testTitle3", "testSubTitle3", true)); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/controller/QuestionE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/controller/QuestionE2ETest.java index e151eae8d..47ea3b514 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/controller/QuestionE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/controller/QuestionE2ETest.java @@ -4,10 +4,8 @@ import com.bang_ggood.question.CustomChecklistFixture; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import io.restassured.http.Header; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; class QuestionE2ETest extends AcceptanceTest { @@ -16,7 +14,7 @@ class QuestionE2ETest extends AcceptanceTest { void readCustomChecklistQuestions() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get("/checklists/questions") .then().log().all() .statusCode(200); @@ -27,7 +25,7 @@ void readCustomChecklistQuestions() { void readAllCustomChecklistQuestion() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get("/custom-checklist/all") .then().log().all() .statusCode(200); @@ -38,7 +36,7 @@ void readAllCustomChecklistQuestion() { void updateCustomChecklist() { RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .body(CustomChecklistFixture.CUSTOM_CHECKLIST_UPDATE_REQUEST()) .when().put("/custom-checklist") .then().log().all() diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/domain/ChecklistQuestionTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/domain/ChecklistQuestionTest.java index 6de1ee365..156a67355 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/domain/ChecklistQuestionTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/domain/ChecklistQuestionTest.java @@ -1,6 +1,9 @@ package com.bang_ggood.question.domain; +import com.bang_ggood.IntegrationTestSupport; import com.bang_ggood.checklist.ChecklistFixture; +import com.bang_ggood.question.ChecklistQuestionFixture; +import com.bang_ggood.question.QuestionFixture; import com.bang_ggood.room.RoomFixture; import com.bang_ggood.room.domain.Room; import com.bang_ggood.user.UserFixture; @@ -10,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; -class ChecklistQuestionTest { +class ChecklistQuestionTest extends IntegrationTestSupport { @DisplayName("체크리스트 내에서 질문끼리 다른 id를 갖고 있는지 확인 성공 : 다른 id일 경우") @Test @@ -18,10 +21,11 @@ void isDifferentQuestionId_true() { //given Room room = RoomFixture.ROOM_1(); User user = UserFixture.USER1(); - ChecklistQuestion checklistQuestion1 = ChecklistFixture.CHECKLIST_QUESTION_1( - ChecklistFixture.CHECKLIST1_USER1(room, user)); - ChecklistQuestion checklistQuestion2 = ChecklistFixture.CHECKLIST_QUESTION_2( - ChecklistFixture.CHECKLIST1_USER1(room, user)); + + ChecklistQuestion checklistQuestion1 = ChecklistQuestionFixture.CHECKLIST1_QUESTION1( + ChecklistFixture.CHECKLIST1_USER1(room, user), QuestionFixture.QUESTION1_CATEGORY1); + ChecklistQuestion checklistQuestion2 = ChecklistQuestionFixture.CHECKLIST1_QUESTION2( + ChecklistFixture.CHECKLIST1_USER1(room, user), QuestionFixture.QUESTION2_CATEGORY1); //when & then assertThat(checklistQuestion1.isDifferentQuestionId(checklistQuestion2)).isTrue(); @@ -33,10 +37,10 @@ void isDifferentQuestionId_false() { //given Room room = RoomFixture.ROOM_1(); User user = UserFixture.USER1(); - ChecklistQuestion checklistQuestion = ChecklistFixture.CHECKLIST_QUESTION_1( - ChecklistFixture.CHECKLIST1_USER1(room, user)); - ChecklistQuestion compareChecklistQuestion = ChecklistFixture.CHECKLIST_QUESTION_1( - ChecklistFixture.CHECKLIST1_USER1(room, user)); + ChecklistQuestion checklistQuestion = ChecklistQuestionFixture.CHECKLIST1_QUESTION1( + ChecklistFixture.CHECKLIST1_USER1(room, user), QuestionFixture.QUESTION1_CATEGORY1); + ChecklistQuestion compareChecklistQuestion = ChecklistQuestionFixture.CHECKLIST1_QUESTION1( + ChecklistFixture.CHECKLIST1_USER1(room, user), QuestionFixture.QUESTION1_CATEGORY1); //when & then assertThat(checklistQuestion.isDifferentQuestionId(compareChecklistQuestion)).isFalse(); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/CategoryRepositoryTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/CategoryRepositoryTest.java new file mode 100644 index 000000000..26eae0c10 --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/CategoryRepositoryTest.java @@ -0,0 +1,49 @@ +package com.bang_ggood.question.repository; + +import com.bang_ggood.IntegrationTestSupport; +import com.bang_ggood.question.QuestionFixture; +import com.bang_ggood.question.domain.CategoryEntity; +import com.bang_ggood.question.domain.CustomChecklistQuestion; +import com.bang_ggood.question.domain.Question; +import com.bang_ggood.user.UserFixture; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + +class CategoryRepositoryTest extends IntegrationTestSupport { + + @Autowired + private CategoryRepository categoryRepository; + @Autowired + private CustomChecklistQuestionRepository customChecklistQuestionRepository; + + @DisplayName("카테고리 조회 성공 : 커스텀 체크리스트 카테고리 조회") + @Test + void findAllById() { + //given + int expectedCategory = 2; + CustomChecklistQuestion customChecklistQuestion1 = new CustomChecklistQuestion( + UserFixture.USER1, + Question.ROOM_CONDITION_1, + QuestionFixture.QUESTION1_CATEGORY1); + CustomChecklistQuestion customChecklistQuestion2 = new CustomChecklistQuestion( + UserFixture.USER1, + Question.ROOM_CONDITION_2, + QuestionFixture.QUESTION2_CATEGORY1); + CustomChecklistQuestion customChecklistQuestion3 = new CustomChecklistQuestion( + UserFixture.USER1, + Question.ROOM_CONDITION_1, + QuestionFixture.QUESTION3_CATEGORY2); + customChecklistQuestionRepository.saveAll(List.of(customChecklistQuestion1, customChecklistQuestion2, customChecklistQuestion3)); + + // when + List categories = categoryRepository.findAllCustomQuestionCategoriesByUserId(UserFixture.USER1.getId()); + + // then + Assertions.assertThat(categories) + .hasSize(expectedCategory) + .containsOnly(QuestionFixture.CATEGORY1, QuestionFixture.CATEGORY2); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/ChecklistQuestionRepositoryTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/ChecklistQuestionRepositoryTest.java index 87cf6130f..bb282da96 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/ChecklistQuestionRepositoryTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/repository/ChecklistQuestionRepositoryTest.java @@ -4,6 +4,8 @@ import com.bang_ggood.checklist.ChecklistFixture; import com.bang_ggood.checklist.domain.Checklist; import com.bang_ggood.checklist.repository.ChecklistRepository; +import com.bang_ggood.question.ChecklistQuestionFixture; +import com.bang_ggood.question.QuestionFixture; import com.bang_ggood.question.domain.ChecklistQuestion; import com.bang_ggood.room.RoomFixture; import com.bang_ggood.room.domain.Room; @@ -39,9 +41,10 @@ void findAllByChecklistId() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - checklistQuestionRepository.save(ChecklistFixture.CHECKLIST_QUESTION_1(checklist)); - checklistQuestionRepository.save(ChecklistFixture.CHECKLIST_QUESTION_2(checklist)); + checklistQuestionRepository.save(ChecklistQuestionFixture.CHECKLIST1_QUESTION1(checklist, QuestionFixture.QUESTION1_CATEGORY1)); + checklistQuestionRepository.save(ChecklistQuestionFixture.CHECKLIST1_QUESTION2(checklist, QuestionFixture.QUESTION2_CATEGORY1)); // when List checklistQuestions = checklistQuestionRepository.findAllByChecklistId( @@ -59,15 +62,16 @@ void deleteAllByChecklistId() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); ChecklistQuestion checklistQuestion1 = checklistQuestionRepository.save( - ChecklistFixture.CHECKLIST_QUESTION_1(checklist)); + ChecklistQuestionFixture.CHECKLIST1_QUESTION1(checklist, QuestionFixture.QUESTION1_CATEGORY1)); ChecklistQuestion checklistQuestion2 = checklistQuestionRepository.save( - ChecklistFixture.CHECKLIST_QUESTION_2(checklist)); + ChecklistQuestionFixture.CHECKLIST1_QUESTION2(checklist, QuestionFixture.QUESTION2_CATEGORY1)); //when checklistQuestionRepository.deleteAllByChecklistId( - ChecklistFixture.CHECKLIST_QUESTION_1(checklist).getChecklist().getId()); + ChecklistQuestionFixture.CHECKLIST1_QUESTION1(checklist, QuestionFixture.QUESTION1_CATEGORY1).getChecklistId()); //then assertAll( diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/ChecklistQuestionServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/ChecklistQuestionServiceTest.java index c1dedc55e..d2f676090 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/ChecklistQuestionServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/ChecklistQuestionServiceTest.java @@ -7,6 +7,7 @@ import com.bang_ggood.global.exception.BangggoodException; import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.question.ChecklistQuestionFixture; +import com.bang_ggood.question.QuestionFixture; import com.bang_ggood.question.domain.Answer; import com.bang_ggood.question.domain.ChecklistQuestion; import com.bang_ggood.question.domain.CustomChecklistQuestion; @@ -23,7 +24,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; import java.util.Collections; import java.util.List; @@ -56,8 +56,9 @@ void createQuestions() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist); + List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist, QuestionFixture.QUESTION1_CATEGORY1, QuestionFixture.QUESTION2_CATEGORY1); //when checklistQuestionService.createQuestions(checklistQuestions); @@ -73,8 +74,9 @@ void createQuestions_duplicateId_exception() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_DUPLICATE(checklist); + List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_DUPLICATE(checklist, QuestionFixture.QUESTION1_CATEGORY1); // when & then assertThatThrownBy( @@ -89,10 +91,11 @@ void deleteAllByChecklistId() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); List checklistQuestions = List.of( - ChecklistQuestionFixture.CHECKLIST1_QUESTION1(checklist), - ChecklistQuestionFixture.CHECKLIST1_QUESTION10(checklist) + ChecklistQuestionFixture.CHECKLIST1_QUESTION1(checklist, QuestionFixture.QUESTION1_CATEGORY1), + ChecklistQuestionFixture.CHECKLIST1_QUESTION2(checklist, QuestionFixture.QUESTION2_CATEGORY1) ); checklistQuestionService.createQuestions(checklistQuestions); @@ -110,16 +113,18 @@ void updateQuestions() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist); + List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist, QuestionFixture.QUESTION1_CATEGORY1, QuestionFixture.QUESTION2_CATEGORY1); checklistQuestionService.createQuestions(checklistQuestions); //when - List updateQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS_UPDATE(checklist); + List updateQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS_UPDATE(checklist, QuestionFixture.QUESTION1_CATEGORY1, QuestionFixture.QUESTION2_CATEGORY1); checklistQuestionService.updateQuestions(checklistQuestions, updateQuestions); //then - assertThat(checklistQuestionRepository.findAllByChecklistId(checklist.getId()).get(1).getAnswer()).isEqualTo(Answer.BAD); + assertThat(checklistQuestionRepository.findAllByChecklistId(checklist.getId()).get(1).getAnswer()).isEqualTo( + Answer.BAD); } @DisplayName("질문 수정 실패: 질문 id가 중복일 경우") @@ -128,12 +133,13 @@ void updateQuestions_duplicateId_exception() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist); + List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist, QuestionFixture.QUESTION1_CATEGORY1, QuestionFixture.QUESTION2_CATEGORY1); checklistQuestionService.createQuestions(checklistQuestions); //when & then - List updateQuestions = ChecklistQuestionFixture.CHECKLIST1_DUPLICATE(checklist); + List updateQuestions = ChecklistQuestionFixture.CHECKLIST1_DUPLICATE(checklist, QuestionFixture.QUESTION1_CATEGORY1); assertThatThrownBy( () -> checklistQuestionService.updateQuestions(checklistQuestions, updateQuestions)) .isInstanceOf(BangggoodException.class) @@ -146,13 +152,14 @@ void updateQuestions_differentQuestionLength_exception() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist); + List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist, QuestionFixture.QUESTION1_CATEGORY1, QuestionFixture.QUESTION2_CATEGORY1); checklistQuestionService.createQuestions(checklistQuestions); //when & then List updateQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS_DIFFERENT_LENGTH( - checklist); + checklist, QuestionFixture.QUESTION1_CATEGORY1); assertThatThrownBy( () -> checklistQuestionService.updateQuestions(checklistQuestions, updateQuestions)) .isInstanceOf(BangggoodException.class) @@ -165,13 +172,14 @@ void updateQuestions_differentQuestion_exception() { //given Room room = roomRepository.save(RoomFixture.ROOM_1()); User user = userRepository.save(UserFixture.USER1()); + Checklist checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); - List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist); + List checklistQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS(checklist, QuestionFixture.QUESTION1_CATEGORY1, QuestionFixture.QUESTION2_CATEGORY1); checklistQuestionService.createQuestions(checklistQuestions); //when & then List updateQuestions = ChecklistQuestionFixture.CHECKLIST1_QUESTIONS_DIFFERENT_QUESTION( - checklist); + checklist, QuestionFixture.QUESTION2_CATEGORY1, QuestionFixture.QUESTION1_CATEGORY1); assertThatThrownBy( () -> checklistQuestionService.updateQuestions(checklistQuestions, updateQuestions)) .isInstanceOf(BangggoodException.class) @@ -183,8 +191,8 @@ void updateQuestions_differentQuestion_exception() { void readCustomChecklistQuestions() { // given User user = userRepository.save(UserFixture.USER1()); - CustomChecklistQuestion question1 = new CustomChecklistQuestion(user, Question.ROOM_CONDITION_5); - CustomChecklistQuestion question2 = new CustomChecklistQuestion(user, Question.BATHROOM_1); + CustomChecklistQuestion question1 = new CustomChecklistQuestion(user, Question.ROOM_CONDITION_5, QuestionFixture.QUESTION1_CATEGORY1); + CustomChecklistQuestion question2 = new CustomChecklistQuestion(user, Question.BATHROOM_1, QuestionFixture.QUESTION2_CATEGORY1); List questions = List.of(question1, question2); customChecklistQuestionRepository.saveAll(questions); diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/QuestionManageServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/QuestionManageServiceTest.java index e8cab4ddd..ebfd3f5d2 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/QuestionManageServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/question/service/QuestionManageServiceTest.java @@ -5,7 +5,7 @@ import com.bang_ggood.global.exception.ExceptionCode; import com.bang_ggood.question.CustomChecklistFixture; import com.bang_ggood.question.domain.CustomChecklistQuestion; -import com.bang_ggood.question.domain.Question; +import com.bang_ggood.question.domain.QuestionEntity; import com.bang_ggood.question.dto.request.CustomChecklistUpdateRequest; import com.bang_ggood.question.dto.response.CategoryQuestionsResponse; import com.bang_ggood.question.dto.response.CustomChecklistQuestionsResponse; @@ -49,8 +49,8 @@ void readChecklistQuestions() { // then List defaultQuestionsIds = customChecklistQuestions.stream() - .map(CustomChecklistQuestion::getQuestion) - .map(Question::getId) + .map(CustomChecklistQuestion::getQuestionEntity) + .map(QuestionEntity::getId) .toList(); List responseQuestionsIds = customChecklistQuestionsResponse.categories().stream() .map(CategoryQuestionsResponse::questions) diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/station/repository/ChecklistRepositoryTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/station/repository/ChecklistRepositoryTest.java new file mode 100644 index 000000000..48ac4258a --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/station/repository/ChecklistRepositoryTest.java @@ -0,0 +1,66 @@ +package com.bang_ggood.station.repository; + +import com.bang_ggood.IntegrationTestSupport; +import com.bang_ggood.checklist.ChecklistFixture; +import com.bang_ggood.checklist.domain.Checklist; +import com.bang_ggood.checklist.repository.ChecklistRepository; +import com.bang_ggood.room.RoomFixture; +import com.bang_ggood.room.domain.Room; +import com.bang_ggood.room.repository.RoomRepository; +import com.bang_ggood.station.domain.ChecklistStation; +import com.bang_ggood.user.UserFixture; +import com.bang_ggood.user.domain.User; +import com.bang_ggood.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ChecklistRepositoryTest extends IntegrationTestSupport { + + @Autowired + RoomRepository roomRepository; + + @Autowired + ChecklistRepository checklistRepository; + + @Autowired + UserRepository userRepository; + + Room room; + User user; + Checklist checklist; + @Autowired + ChecklistStationRepository checklistStationRepository; + + @BeforeEach + void setUp() { + room = roomRepository.save(RoomFixture.ROOM_1()); + user = userRepository.save(UserFixture.USER1()); + checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); + } + + @DisplayName("체크리스트 아이디를 통한 조회 성공") + @Test + void findByChecklistId() { + // given + ChecklistStation checklistStation1 = new ChecklistStation(checklist, "잠실", "2호선", 5); + ChecklistStation checklistStation2 = new ChecklistStation(checklist, "잠실", "8호선", 6); + checklistStationRepository.saveAll(List.of(checklistStation1, checklistStation2)); + + // when & then + assertThat(checklistStationRepository.findByChecklist(checklist)) + .containsExactlyInAnyOrder(checklistStation1, checklistStation2); + } + + @DisplayName("체크리스트 아이디를 통한 조회 실패: 저장된 값이 없는 경우") + @Test + void findByChecklistId_noData_exception() { + // given & when & then + assertThat(checklistStationRepository.findByChecklist(checklist)) + .isEmpty(); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/station/service/ChecklistStationServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/station/service/ChecklistStationServiceTest.java new file mode 100644 index 000000000..522788bfa --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/station/service/ChecklistStationServiceTest.java @@ -0,0 +1,73 @@ +package com.bang_ggood.station.service; + +import com.bang_ggood.IntegrationTestSupport; +import com.bang_ggood.checklist.ChecklistFixture; +import com.bang_ggood.checklist.domain.Checklist; +import com.bang_ggood.checklist.repository.ChecklistRepository; +import com.bang_ggood.room.RoomFixture; +import com.bang_ggood.room.domain.Room; +import com.bang_ggood.room.repository.RoomRepository; +import com.bang_ggood.station.domain.ChecklistStation; +import com.bang_ggood.station.repository.ChecklistStationRepository; +import com.bang_ggood.user.UserFixture; +import com.bang_ggood.user.domain.User; +import com.bang_ggood.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ChecklistStationServiceTest extends IntegrationTestSupport { + + @Autowired + RoomRepository roomRepository; + + @Autowired + ChecklistRepository checklistRepository; + + @Autowired + UserRepository userRepository; + + @Autowired + ChecklistStationService checklistStationService; + + @Autowired + ChecklistStationRepository checklistStationRepository; + + Room room; + User user; + Checklist checklist; + + @BeforeEach + void setUp() { + room = roomRepository.save(RoomFixture.ROOM_1()); + user = userRepository.save(UserFixture.USER1()); + checklist = checklistRepository.save(ChecklistFixture.CHECKLIST1_USER1(room, user)); + } + + @DisplayName("ChecklistStation 객체 생성 성공") + @Test + void createChecklistStations() { + // given & when + checklistStationService.createChecklistStations(checklist, 38, 127); + + // then + assertThat(checklistStationRepository.findByChecklist(checklist)).isNotEmpty(); + } + + @DisplayName("ChecklistStation 조회 성공") + @Test + void readChecklistStations() { + // given + ChecklistStation checklistStation1 = new ChecklistStation(checklist, "잠실", "2호선", 5); + ChecklistStation checklistStation2 = new ChecklistStation(checklist, "잠실", "8호선", 6); + checklistStationRepository.saveAll(List.of(checklistStation1, checklistStation2)); + + // when & then + assertThat(checklistStationService.readChecklistStationsByChecklist(checklist)) + .containsExactlyInAnyOrder(checklistStation1, checklistStation2); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/station/SubwayStationServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/station/service/SubwayStationServiceTest.java similarity index 88% rename from backend/bang-ggood/src/test/java/com/bang_ggood/station/SubwayStationServiceTest.java rename to backend/bang-ggood/src/test/java/com/bang_ggood/station/service/SubwayStationServiceTest.java index 4d9b6b5ec..e54ca72a8 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/station/SubwayStationServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/station/service/SubwayStationServiceTest.java @@ -1,8 +1,7 @@ -package com.bang_ggood.station; +package com.bang_ggood.station.service; import com.bang_ggood.IntegrationTestSupport; -import com.bang_ggood.station.dto.SubwayStationResponse; -import com.bang_ggood.station.service.SubwayStationService; +import com.bang_ggood.station.dto.response.SubwayStationResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -19,15 +18,31 @@ public class SubwayStationServiceTest extends IntegrationTestSupport { @Autowired SubwayStationService subwayStationService; + // check data in "https://apis.map.kakao.com/web/sample/addMapClickEventWithMarker/" + private static Stream provideStationData() { + return Stream.of( + Arguments.of(37.517406150696104, 127.10333134512422, + new Station("잠실(송파구청)", List.of("2호선", "8호선")), + new Station("잠실나루", List.of("2호선"))), + Arguments.of(37.50808977595056, 127.04649615866747, + new Station("선정릉", List.of("9호선", "수인분당선")), + new Station("선릉", List.of("2호선", "수인분당선"))), + Arguments.of(37.538999998345446, 126.97201837726666, + new Station("남영", List.of("1호선")), + new Station("삼각지", List.of("4호선", "6호선"))) + ); + } + @DisplayName("가까운 지하철 2개 조회 성공") @ParameterizedTest @MethodSource("provideStationData") void readNearestStation(double latitude, double longitude, Station nearestStation, Station nextNearestStation) { // given & when - List responses = subwayStationService.readNearestStation(latitude, longitude); + List responses = subwayStationService.readNearestStation(latitude, longitude) + .getStations(); assertThat(responses).hasSize(2); - SubwayStationResponse nearest = responses.get(0); - SubwayStationResponse nextNearest = responses.get(1); + SubwayStationResponse nearest = responses.get(0); + SubwayStationResponse nextNearest = responses.get(1); // then assertAll(() -> { @@ -39,21 +54,6 @@ void readNearestStation(double latitude, double longitude, Station nearestStatio ); } - // check data in "https://apis.map.kakao.com/web/sample/addMapClickEventWithMarker/" - private static Stream provideStationData() { - return Stream.of( - Arguments.of(37.517406150696104, 127.10333134512422, - new Station("잠실(송파구청)", List.of("2호선", "8호선")), - new Station("잠실나루", List.of("2호선"))), - Arguments.of(37.50808977595056, 127.04649615866747, - new Station("선정릉", List.of("9호선", "수인분당선")), - new Station("선릉", List.of("2호선", "수인분당선"))), - Arguments.of(37.538999998345446, 126.97201837726666, - new Station("남영", List.of("1호선")), - new Station("삼각지", List.of("4호선", "6호선"))) - ); - } - private static class Station { String name; List lines; diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/UserFixture.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/UserFixture.java index 520521e8f..2e84fd375 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/user/UserFixture.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/UserFixture.java @@ -3,21 +3,29 @@ import com.bang_ggood.auth.dto.response.KakaoAccountResponse; import com.bang_ggood.auth.dto.response.OauthInfoApiResponse; import com.bang_ggood.auth.dto.response.ProfileResponse; +import com.bang_ggood.user.domain.LoginType; import com.bang_ggood.user.domain.User; import com.bang_ggood.user.domain.UserType; +import com.bang_ggood.user.repository.UserRepository; public class UserFixture { + public static User USER1; + public static User USER1() { - return new User("방방이", "bang-bang@gmail.com", UserType.USER); + return new User("방방이", "bang-bang01@gmail.com", "password1234", UserType.USER, LoginType.LOCAL); } public static User USER2() { - return new User("빵빵이", "bbang-bbang@gmail.com", UserType.USER); + return new User("빵빵이", "bbang-bbang@gmail.com", "password1234", UserType.USER, LoginType.LOCAL); + } + + public static User GUEST_USER1() { + return new User("빵빵이", "bbang-bbang1@gmail.com", UserType.GUEST, LoginType.LOCAL); } - public static User GUEST_USER() { - return new User("빵빵이", "bbang-bbang@gmail.com", UserType.GUEST); + public static User GUEST_USER2() { + return new User("빵빵이", "bbang-bbang2@gmail.com", UserType.GUEST, LoginType.LOCAL); } public static User USER1_WITH_ID() { @@ -30,13 +38,17 @@ public static User USER2_WITH_ID() { public static OauthInfoApiResponse OAUTH_INFO_RESPONSE_USER1() { return new OauthInfoApiResponse("", "", - new KakaoAccountResponse(USER1().getEmail(), USER1().getName(), + new KakaoAccountResponse(USER1().getEmail().getValue(), USER1().getName(), new ProfileResponse("", "", ""))); } public static OauthInfoApiResponse OAUTH_INFO_RESPONSE_USER2() { return new OauthInfoApiResponse("", "", - new KakaoAccountResponse(USER2().getEmail(), USER2().getName(), + new KakaoAccountResponse(USER2().getEmail().getValue(), USER2().getName(), new ProfileResponse("", "", ""))); } + + public static void init(UserRepository userRepository) { + USER1 = userRepository.save(new User("방방이", "bang-bang@gmail.com", UserType.USER, LoginType.LOCAL)); + } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/controller/UserE2ETest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/controller/UserE2ETest.java index f34edb762..39047a2c4 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/user/controller/UserE2ETest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/controller/UserE2ETest.java @@ -5,10 +5,8 @@ import com.bang_ggood.user.dto.UserResponse; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import io.restassured.http.Header; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -21,7 +19,7 @@ void readUserInfo() { User user = this.getAuthenticatedUser(); UserResponse response = RestAssured.given().log().all() .contentType(ContentType.JSON) - .header(new Header(HttpHeaders.COOKIE, this.responseCookie.toString())) + .headers(this.headers) .when().get("/user/me") .then().log().all() .statusCode(200) @@ -31,7 +29,7 @@ void readUserInfo() { assertAll( () -> assertThat(response.userId()).isEqualTo(user.getId()), () -> assertThat(response.userName()).isEqualTo(user.getName()), - () -> assertThat(response.userEmail()).isEqualTo(user.getEmail()) + () -> assertThat(response.userEmail()).isEqualTo(user.getEmail().getValue()) ); } } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/EmailTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/EmailTest.java new file mode 100644 index 000000000..2160828e9 --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/EmailTest.java @@ -0,0 +1,66 @@ +package com.bang_ggood.user.domain; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EmailTest { + + @DisplayName("이메일 형식에 맞는 이메일 생성 성공") + @Test + void createEmail() { + assertThatCode(() -> new Email("abc@gmail.com")) + .doesNotThrowAnyException(); + } + + @DisplayName("서브 도메인 존재할 시 이메일 생성 성공") + @Test + void createEmail_subDomain() { + assertThatCode(() -> new Email("abc@mail.gmail.com")) + .doesNotThrowAnyException(); + } + + @DisplayName("이메일 생성 실패 : local-part가 존재하지 않는 경우") + @Test + void createPassword_emptyLocalPart_exception() { + assertThatThrownBy(() -> new Email("@gmail.com")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.EMAIL_INVALID_FORMAT.getMessage()); + } + + @DisplayName("이메일 생성 실패 : 2차 도메인이 존재하지 않는 경우") + @Test + void createPassword_emptySecondLevelDomain_exception() { + assertThatThrownBy(() -> new Email("abc@.com")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.EMAIL_INVALID_FORMAT.getMessage()); + } + + @DisplayName("이메일 생성 실패 : 최상위 도메인이 존재하지 않는 경우") + @Test + void createPassword_emptyTopLevelDomain_exception() { + assertThatThrownBy(() -> new Email("abc@gmail")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.EMAIL_INVALID_FORMAT.getMessage()); + } + + @DisplayName("이메일 생성 실패 : 최상위 도메인이 한글자인 경우") + @Test + void createPassword_oneWordTopLevelDomain_exception() { + assertThatThrownBy(() -> new Email("abc@gmail.g")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.EMAIL_INVALID_FORMAT.getMessage()); + } + + @DisplayName("이메일 생성 실패 : 공백이 포함된 경우") + @Test + void createPassword_withWhitespace_exception() { + assertThatThrownBy(() -> new Email("abc @gmail.com")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.EMAIL_INVALID_FORMAT.getMessage()); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/PasswordTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/PasswordTest.java new file mode 100644 index 000000000..e23bdd0cb --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/PasswordTest.java @@ -0,0 +1,82 @@ +package com.bang_ggood.user.domain; + +import com.bang_ggood.global.exception.BangggoodException; +import com.bang_ggood.global.exception.ExceptionCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PasswordTest { + + @DisplayName("영어와 숫자를 모두 포함하고 6자 이상인 비밀번호 생성 성공") + @Test + void createPassword() { + assertThatCode(() -> new Password("password1234")) + .doesNotThrowAnyException(); + } + + @DisplayName("비밀번호 생성 실패 : null일 경우") + @Test + void createPassword_null_exception() { + assertThatThrownBy(() -> new Password(null)) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.PASSWORD_INVALID_FORMAT.getMessage()); + } + + @DisplayName("비밀번호 생성 실패 : 영어가 포함되지 않은 경우") + @Test + void createPassword_notContainEnglish_exception() { + assertThatThrownBy(() -> new Password("123456")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.PASSWORD_INVALID_FORMAT.getMessage()); + } + + @DisplayName("비밀번호 생성 실패 : 숫자가 포함되지 않은 경우") + @Test + void createPassword_notContainNumber_exception() { + assertThatThrownBy(() -> new Password("password")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.PASSWORD_INVALID_FORMAT.getMessage()); + } + + @DisplayName("비밀번호 생성 실패 : 영어와 숫자가 포함되지 않은 경우") + @Test + void createPassword_notContainEnglishAndNumber_exception() { + assertThatThrownBy(() -> new Password("!@#$%^")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.PASSWORD_INVALID_FORMAT.getMessage()); + } + + @DisplayName("비밀번호 생성 실패 : 길이가 6자 미만인 경우") + @Test + void createPassword_lessThan6_exception() { + assertThatThrownBy(() -> new Password("pas12")) + .isInstanceOf(BangggoodException.class) + .hasMessage(ExceptionCode.PASSWORD_INVALID_FORMAT.getMessage()); + } + + @DisplayName("비밀번호 다름 확인: 일치하는 경우") + @Test + void isDifferent_false() { + // given + String passwordValue = "password1234"; + Password password = new Password(passwordValue); + + // when & then + assertThat(password.isDifferent(passwordValue)).isFalse(); + } + + @DisplayName("비밀번호 다름 확인: 일치하지 않는 경우") + @Test + void isDifferent_true() { + // given + String passwordValue = "password1234"; + Password password = new Password(passwordValue); + + // when & then + assertThat(password.isDifferent(passwordValue)).isFalse(); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/UserTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/UserTest.java new file mode 100644 index 000000000..96cb72e2d --- /dev/null +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/domain/UserTest.java @@ -0,0 +1,32 @@ +package com.bang_ggood.user.domain; + +import com.bang_ggood.user.UserFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class UserTest { + + @DisplayName("비밀번호 불일치 확인 성공: 일치하는 경우") + @Test + void passwordEqual_false() { + //given + User user = UserFixture.USER1(); + String password = "password1234"; + + //when & then + assertThat(user.isDifferent(password)).isFalse(); + } + + @DisplayName("비밀번호 불일치 확인 성공: 일치하지 않는 경우") + @Test + void passwordEqual_true() { + //given + User user = UserFixture.USER1(); + String password = "passwords12345"; + + //when & then + assertThat(user.isDifferent(password)).isTrue(); + } +} diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java index 7acb287fe..92cdcc8b6 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java @@ -16,15 +16,15 @@ class UserRepositoryTest extends IntegrationTestSupport { @Autowired private UserRepository userRepository; - @DisplayName("유저 이메일 조회 성공 : 유저 삭제 후 유저를 조회하면(논리적 삭제) 조회되지 않는다.") + @DisplayName("유저 이메일 및 로그인 타입 조회 성공 : 유저 삭제 후 유저를 조회하면(논리적 삭제) 조회되지 않는다.") @Test void findByEmail() { // given User user = userRepository.save(UserFixture.USER1()); - userRepository.deleteByUser(user); + userRepository.deleteById(user.getId()); // when - Optional findUser = userRepository.findByEmail(user.getEmail()); + Optional findUser = userRepository.findByEmailAndLoginType(user.getEmail(), user.getLoginType()); // then Assertions.assertThat(findUser).isEmpty(); @@ -34,12 +34,26 @@ void findByEmail() { @Test void findByType() { // given - User expectedUser = userRepository.save(UserFixture.GUEST_USER()); + User expectedUser = userRepository.save(UserFixture.GUEST_USER1()); // when - List users = userRepository.findUserByType(UserType.GUEST); + List users = userRepository.findUserByUserType(UserType.GUEST); // then Assertions.assertThat(users).containsExactly(expectedUser); } + + @DisplayName("논리적 삭제 성공") + @Test + void deleteById() { + // given + User user = userRepository.save(UserFixture.USER1()); + + // when + userRepository.deleteById(user.getId()); + + // then + Optional findUser = userRepository.findById(user.getId()); + Assertions.assertThat(findUser).isEmpty(); + } } diff --git a/backend/bang-ggood/src/test/resources/data-test.sql b/backend/bang-ggood/src/test/resources/data-test.sql index 4dd79214f..84761f800 100644 --- a/backend/bang-ggood/src/test/resources/data-test.sql +++ b/backend/bang-ggood/src/test/resources/data-test.sql @@ -1,19 +1,103 @@ -INSERT INTO users(name, email, type, created_at, modified_at, deleted) -VALUES ('방방이', 'bang-ggood@gmail.com', 'USER', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false); +-- 비밀번호 : password1234 +INSERT INTO users(name, email, password, user_type, login_type, created_at, modified_at, deleted) +VALUES ('방방이', 'bang-ggood@gmail.com', + 'xDNYKEJqE/36U0Dt3nXRMFPNEMEgjCYM7R/A4B29baOsv4KYQ9MGgcO3HUa11sNKCFb9ZXyYBqJqxNglvBzFvg==:7yejAszEpxBb7AyZNKvAqpmMEJiKFXIa8JKwAx3n4loB2DRcAC2pfwkgo/dzKzRvBX4RbrATWaIlPYrgAhbHZQ==', + 'USER', 'LOCAL', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false); -INSERT INTO custom_checklist_question(user_id, question, created_at, modified_at, deleted) -VALUES (1, 'ROOM_CONDITION_1', '2024-07-22 07:56:42', '2024-07-22 07:56:42', false), - (1, 'ROOM_CONDITION_2', '2024-07-22 07:56:43', '2024-07-22 07:56:43', false), - (1, 'ROOM_CONDITION_3', '2024-07-22 07:56:44', '2024-07-22 07:56:44', false), - (1, 'ROOM_CONDITION_4', '2024-07-22 07:56:45', '2024-07-22 07:56:45', false), - (1, 'ROOM_CONDITION_5', '2024-07-22 07:56:46', '2024-07-22 07:56:46', false), - (1, 'WINDOW_1', '2024-07-22 07:56:47', '2024-07-22 07:56:47', false), - (1, 'WINDOW_2', '2024-07-22 07:56:48', '2024-07-22 07:56:48', false), - (1, 'WINDOW_3', '2024-07-22 07:56:49', '2024-07-22 07:56:49', false), - (1, 'WINDOW_4', '2024-07-22 07:56:50', '2024-07-22 07:56:50', false), - (1, 'BATHROOM_1', '2024-07-22 07:56:51', '2024-07-22 07:56:51', false), - (1, 'BATHROOM_2', '2024-07-22 07:56:52', '2024-07-22 07:56:52', false), - (1, 'BATHROOM_3', '2024-07-22 07:56:53', '2024-07-22 07:56:53', false), - (1, 'SECURITY_1', '2024-07-22 07:56:54', '2024-07-22 07:56:54', false), - (1, 'SECURITY_2', '2024-07-22 07:56:55', '2024-07-22 07:56:55', false), - (1, 'SECURITY_3', '2024-07-22 07:56:56', '2024-07-22 07:56:56', false); +INSERT INTO category (name) +VALUES + ('방 컨디션'), + ('창문'), + ('화장실'), + ('보안'), + ('외부'); + +INSERT INTO question (category_id, title, subtitle, is_default) +VALUES + (1, '곰팡이가 핀 곳 없이 깨끗한가요?', '천장, 벽면, 가구 뒤, 장판을 확인하세요.', true), + (1, '불쾌한 냄새 없이 쾌적한가요?', null, true), + (1, '벌레가 나온 흔적 없이 깔끔한가요?', '벌레 퇴치약이 부착되어 있는지 확인하세요.', true), + (1, '물건을 충분히 수납할 수 있는 공간이 있나요?', null, true), + (1, '방 인테리어는 괜찮나요?', null, true), + (1, '에어컨의 상태는 괜찮은가요?', '에어컨을 틀어서 불쾌한 냄새가 나진 않는지 확인하세요.', false), + (1, '보일러가 잘 동작하나요?', null, false), + (1, '콘센트 위치와 개수가 적절한가요?', null, false), + (1, '벽지 상태가 양호한가요?', null, false), + (2, '창 밖의 뷰가 가로막힘 없이 트여있나요?', null, true), + (2, '창문 상태가 괜찮나요?', null, true), + (2, '환기가 잘 되는 구조인가요?', '창문 크기와 방향을 확인하세요.', true), + (2, '햇빛이 잘 들어오나요?', null, true), + (2, '창문이 이중창인가요?', null, false), + (2, '창문 밖에 쓰레기통 등 냄새가 나는 요소가 있나요?', null, false), + (3, '화장실이 깨끗한가요?', '청소 가능한 얼룩인지 확인하세요.', true), + (3, '수압 및 물 빠짐이 괜찮은가요?', '화장실에서 수도와 변기를 동시에 사용해보세요.', true), + (3, '환기 시설이 있나요?', null, true), + (3, '내부에 창문이 있나요?', null, false), + (3, '온수가 잘 나오나요?', null, false), + (4, '잠금장치가 있는 공동 현관문이 있나요?', null, true), + (4, '출입구와 복도에 CCTV가 설치되어 있나요?', null, true), + (4, '관리자분이 함께 상주하시나요?', '관리자분이 24시간 상주하시는지 확인하세요.', true), + (4, '보안 시설이 잘 갖추어져 있나요?', '도어락, 창문 잠금장치 등이 있는지 확인하세요.', false), + (4, '화면이 달린 인터폰이 제공되나요?', null, false), + (4, '현관문에 걸쇠가 있나요?', null, false), + (5, '주변 도로가 밤에도 충분히 밝은가요?', null, false), + (5, '주변에 소음 시설이 있는지 확인했나요?', '유흥시설, 놀이터, 공사장이 있는지 확인하세요.', false), + (5, '1층에 음식점이 있는지 확인했나요?', null, false), + (5, '집 가는 길이 언덕 없이 완만한가요?', null, false), + (5, '옆 건물에서 보이는 구조인지 확인했나요?', null, false), + (5, '주차할 수 있는 시설이 있나요?', null, false) +; +INSERT INTO highlight (question_id, name) +VALUES + (1, '곰팡이'), + (2, '불쾌한 냄새'), + (3, '벌레'), + (4, '수납할 수 있는 공간'), + (5, '방 인테리어'), + (6, '에어컨'), + (7, '보일러'), + (8, '콘센트'), + (9, '벽지 상태'), + (10, '창 밖의 뷰'), + (11, '창문 상태'), + (12, '환기'), + (13, '햇빛'), + (14, '이중창'), + (15, '냄새가 나는 요소'), + (16, '깨끗'), + (17, '수압 및 물 빠짐'), + (18, '환기 시설'), + (19, '창문'), + (20, '온수'), + (21, '잠금장치'), + (21, '공동 현관문'), + (22, 'CCTV'), + (23, '관리자분'), + (24, '보안 시설'), + (25, '인터폰'), + (26, '걸쇠'), + (27, '주변 도로'), + (27, '밝은가요'), + (28, '소음 시설'), + (29, '음식점'), + (30, '언덕'), + (31, '보이는 구조'), + (32, '주차할 수 있는 시설') +; + +INSERT INTO custom_checklist_question(user_id, question, question_id, created_at, modified_at, deleted) +VALUES (1, 'ROOM_CONDITION_1',1, '2024-07-22 07:56:42', '2024-07-22 07:56:42', false), + (1, 'ROOM_CONDITION_2',2, '2024-07-22 07:56:43', '2024-07-22 07:56:43', false), + (1, 'ROOM_CONDITION_3',3, '2024-07-22 07:56:44', '2024-07-22 07:56:44', false), + (1, 'ROOM_CONDITION_4',4, '2024-07-22 07:56:45', '2024-07-22 07:56:45', false), + (1, 'ROOM_CONDITION_5',5, '2024-07-22 07:56:46', '2024-07-22 07:56:46', false), + (1, 'WINDOW_1',6, '2024-07-22 07:56:47', '2024-07-22 07:56:47', false), + (1, 'WINDOW_2',7, '2024-07-22 07:56:48', '2024-07-22 07:56:48', false), + (1, 'WINDOW_3',8, '2024-07-22 07:56:49', '2024-07-22 07:56:49', false), + (1, 'WINDOW_4',9, '2024-07-22 07:56:50', '2024-07-22 07:56:50', false), + (1, 'BATHROOM_1',10, '2024-07-22 07:56:51', '2024-07-22 07:56:51', false), + (1, 'BATHROOM_2',11, '2024-07-22 07:56:52', '2024-07-22 07:56:52', false), + (1, 'BATHROOM_3',12, '2024-07-22 07:56:53', '2024-07-22 07:56:53', false), + (1, 'SECURITY_1',13, '2024-07-22 07:56:54', '2024-07-22 07:56:54', false), + (1, 'SECURITY_2',14, '2024-07-22 07:56:55', '2024-07-22 07:56:55', false), + (1, 'SECURITY_3',15, '2024-07-22 07:56:56', '2024-07-22 07:56:56', false); diff --git a/backend/bang-ggood/src/test/resources/schema-test.sql b/backend/bang-ggood/src/test/resources/schema-test.sql index b3efc3e22..3338af408 100644 --- a/backend/bang-ggood/src/test/resources/schema-test.sql +++ b/backend/bang-ggood/src/test/resources/schema-test.sql @@ -1,16 +1,45 @@ -- Drop tables if they exist -DROP TABLE IF EXISTS checklist CASCADE; +DROP TABLE IF EXISTS test_entity CASCADE; +DROP TABLE IF EXISTS checklist_station CASCADE; +DROP TABLE IF EXISTS checklist_like CASCADE; +DROP TABLE IF EXISTS custom_checklist_question CASCADE; DROP TABLE IF EXISTS checklist_option CASCADE; DROP TABLE IF EXISTS checklist_question CASCADE; DROP TABLE IF EXISTS checklist_maintenance CASCADE; -DROP TABLE IF EXISTS room CASCADE; -DROP TABLE IF EXISTS users CASCADE; -DROP TABLE IF EXISTS test_entity CASCADE; -DROP TABLE IF EXISTS custom_checklist_question CASCADE; -DROP TABLE IF EXISTS checklist_like CASCADE; +DROP TABLE IF EXISTS checklist CASCADE; DROP TABLE IF EXISTS article CASCADE; +DROP TABLE IF EXISTS users CASCADE; +DROP TABLE IF EXISTS room CASCADE; +DROP TABLE IF EXISTS highlight CASCADE; +DROP TABLE IF EXISTS question CASCADE; +DROP TABLE IF EXISTS category CASCADE; -- Create tables + +CREATE TABLE category +( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) +); + +CREATE TABLE question +( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + category_id INTEGER, + title VARCHAR(255), + subtitle VARCHAR(255), + is_default BOOLEAN, + FOREIGN KEY (category_id) REFERENCES category (id) +); + +CREATE TABLE highlight +( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + question_id INTEGER, + name VARCHAR(255), + FOREIGN KEY (question_id) REFERENCES question (id) +); + CREATE TABLE room ( id BIGINT AUTO_INCREMENT PRIMARY KEY, @@ -33,10 +62,13 @@ CREATE TABLE users id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255) NOT NULL, - type VARCHAR(255) NOT NULL, + password VARCHAR(255), + user_type VARCHAR(255) NOT NULL, + login_type VARCHAR(255) NOT NULL, created_at TIMESTAMP(6), modified_at TIMESTAMP(6), - deleted BOOLEAN + deleted BOOLEAN, + CONSTRAINT unique_email_login_type UNIQUE (email, login_type) ); CREATE TABLE checklist @@ -75,14 +107,15 @@ CREATE TABLE checklist_question ( id BIGINT AUTO_INCREMENT PRIMARY KEY, question VARCHAR(255) NOT NULL, + question_id INTEGER NOT NULL, checklist_id BIGINT NOT NULL, answer VARCHAR(255), created_at TIMESTAMP(6), modified_at TIMESTAMP(6), deleted BOOLEAN, - FOREIGN KEY (checklist_id) REFERENCES checklist (id) + FOREIGN KEY (checklist_id) REFERENCES checklist (id), + FOREIGN KEY (question_id) REFERENCES question (id) ); - CREATE TABLE checklist_option ( id BIGINT AUTO_INCREMENT PRIMARY KEY, @@ -99,10 +132,12 @@ CREATE TABLE custom_checklist_question id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT, question VARCHAR(255), + question_id INTEGER, created_at TIMESTAMP(6), modified_at TIMESTAMP(6), deleted BOOLEAN, - FOREIGN KEY (user_id) references users (id) + FOREIGN KEY (user_id) references users (id), + FOREIGN KEY (question_id) references question (id) ); CREATE TABLE test_entity @@ -137,3 +172,17 @@ CREATE TABLE article modified_at TIMESTAMP(6), deleted BOOLEAN ); + +CREATE TABLE checklist_station +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + checklist_id BIGINT, + station_name VARCHAR(255), + station_line VARCHAR(255), + walking_time INTEGER, + created_at TIMESTAMP(6), + modified_at TIMESTAMP(6), + deleted BOOLEAN, + FOREIGN KEY (checklist_id) REFERENCES checklist (id) +); +