diff --git a/build.gradle b/build.gradle index f607ed6..28c6679 100644 --- a/build.gradle +++ b/build.gradle @@ -32,11 +32,17 @@ dependencies { // H2 runtimeOnly 'com.h2database:h2' + // MySQL + runtimeOnly 'com.mysql:mysql-connector-j' + // JWT (Java JSON Web Token) implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + // Lombok (Optional) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/kuit/kuit4serverauth/argumentResolver/UserInfoArgumentResolver.java b/src/main/java/com/kuit/kuit4serverauth/argumentResolver/UserInfoArgumentResolver.java new file mode 100644 index 0000000..dd72a11 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/argumentResolver/UserInfoArgumentResolver.java @@ -0,0 +1,27 @@ +package com.kuit.kuit4serverauth.argumentResolver; + +import com.kuit.kuit4serverauth.dto.UserInfo; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(UserInfo.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + String username = (String)request.getAttribute("username"); + String role = (String)request.getAttribute("role"); + return new UserInfo(username, role); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/config/SwaggerConfig.java b/src/main/java/com/kuit/kuit4serverauth/config/SwaggerConfig.java new file mode 100644 index 0000000..24d7ffd --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/config/SwaggerConfig.java @@ -0,0 +1,19 @@ +package com.kuit.kuit4serverauth.config; + +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.OpenAPI; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(info); + } + + Info info = new Info().title("Kuit4 Server Auth").version("0.0.1").description( + "

Kuit4 Server Auth

"); +} diff --git a/src/main/java/com/kuit/kuit4serverauth/config/WebConfig.java b/src/main/java/com/kuit/kuit4serverauth/config/WebConfig.java index a0b47b4..3eb2413 100644 --- a/src/main/java/com/kuit/kuit4serverauth/config/WebConfig.java +++ b/src/main/java/com/kuit/kuit4serverauth/config/WebConfig.java @@ -1,20 +1,30 @@ package com.kuit.kuit4serverauth.config; +import com.kuit.kuit4serverauth.argumentResolver.UserInfoArgumentResolver; import com.kuit.kuit4serverauth.interceptor.AuthInterceptor; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.List; + @Configuration +@RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final AuthInterceptor authInterceptor; + private final UserInfoArgumentResolver userInfoArgumentResolver; - public WebConfig(AuthInterceptor authInterceptor) { - this.authInterceptor = authInterceptor; + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/profile") + .addPathPatterns("/admin"); } @Override - public void addInterceptors(InterceptorRegistry registry) { - // TODO /profile, /admin 앞에 붙이기 + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(userInfoArgumentResolver); } } diff --git a/src/main/java/com/kuit/kuit4serverauth/controller/AuthController.java b/src/main/java/com/kuit/kuit4serverauth/controller/AuthController.java index e8f7e24..d22d6ba 100644 --- a/src/main/java/com/kuit/kuit4serverauth/controller/AuthController.java +++ b/src/main/java/com/kuit/kuit4serverauth/controller/AuthController.java @@ -1,43 +1,32 @@ package com.kuit.kuit4serverauth.controller; -import com.kuit.kuit4serverauth.exception.CustomException; -import com.kuit.kuit4serverauth.exception.ErrorCode; -import com.kuit.kuit4serverauth.model.User; +import com.kuit.kuit4serverauth.dto.LoginRequest; +import com.kuit.kuit4serverauth.dto.TokenDTO; import com.kuit.kuit4serverauth.repository.UserRepository; +import com.kuit.kuit4serverauth.service.AuthService; import com.kuit.kuit4serverauth.service.JwtUtil; -import org.springframework.http.HttpStatus; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import java.util.HashMap; -import java.util.Map; - @RestController +@RequiredArgsConstructor public class AuthController { private final UserRepository userRepository; private final JwtUtil jwtUtil; - - public AuthController(UserRepository userRepository, JwtUtil jwtUtil) { - this.userRepository = userRepository; - this.jwtUtil = jwtUtil; - } + private final AuthService authService; @PostMapping("/login") - public ResponseEntity> login(@RequestBody Map credentials) { - String username = credentials.get("username"); - String password = credentials.get("password"); - - User user = userRepository.findByUsername(username); - if (user == null || !user.getPassword().equals(password)) { - throw new CustomException(ErrorCode.INVALID_USERNAME_OR_PASSWORD); - } + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + return ResponseEntity.ok(authService.login(loginRequest)); + } - String token = jwtUtil.generateToken(user.getUsername(), user.getRole()); - Map response = new HashMap<>(); - response.put("token", token); - return ResponseEntity.ok(response); + // 해당 url로 요청을 보내면 RefreshToken을 통해 AccessToken을 새로 발급해줌. + @PostMapping("/refresh-token") + public ResponseEntity refreshToken(@RequestBody TokenDTO tokenRequest) { + return ResponseEntity.ok(authService.refresh(tokenRequest)); } } diff --git a/src/main/java/com/kuit/kuit4serverauth/controller/CategoryController.java b/src/main/java/com/kuit/kuit4serverauth/controller/CategoryController.java new file mode 100644 index 0000000..2f46920 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/controller/CategoryController.java @@ -0,0 +1,24 @@ +package com.kuit.kuit4serverauth.controller; + +import com.kuit.kuit4serverauth.dto.StoreCategoryOrderCountDTO; +import com.kuit.kuit4serverauth.service.CategoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class CategoryController { + + private final CategoryService categoryService; + + @GetMapping("/categories/{category_id}/most-ordered-store") + public ResponseEntity> getMostOrderedStoreByCategory(@PathVariable("category_id") Long category_id) { + List mostOrderedStore = categoryService.findMostOrderedStoreByCategory(category_id); + return ResponseEntity.ok(mostOrderedStore); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/controller/MenuController.java b/src/main/java/com/kuit/kuit4serverauth/controller/MenuController.java new file mode 100644 index 0000000..d2576fd --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/controller/MenuController.java @@ -0,0 +1,26 @@ +package com.kuit.kuit4serverauth.controller; + +import com.kuit.kuit4serverauth.dto.MenuStoreDTO; +import com.kuit.kuit4serverauth.service.MenuService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +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 +@RequiredArgsConstructor +public class MenuController { + + private final MenuService menuService; + + @GetMapping("/menus") + public ResponseEntity> search(@RequestParam("menuName") String menuName) { + List list = menuService.findMenuAndStoreNameByMenuName(menuName); + + return ResponseEntity.ok(list); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/controller/StoreController.java b/src/main/java/com/kuit/kuit4serverauth/controller/StoreController.java new file mode 100644 index 0000000..74937e3 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/controller/StoreController.java @@ -0,0 +1,28 @@ +package com.kuit.kuit4serverauth.controller; + +import com.kuit.kuit4serverauth.model.Store; +import com.kuit.kuit4serverauth.service.StoreService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +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 +@RequiredArgsConstructor +public class StoreController { + + private final StoreService storeService; + + @GetMapping("/stores") + public ResponseEntity> getStores(@RequestParam("minimumOrderPrice") Integer minimumOrderPrice, + @RequestParam("status") String status) { + List stores = storeService.findStoresByConditions(minimumOrderPrice, status); + + return ResponseEntity.ok(stores); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/controller/UserController.java b/src/main/java/com/kuit/kuit4serverauth/controller/UserController.java index 18cb7af..ed6c009 100644 --- a/src/main/java/com/kuit/kuit4serverauth/controller/UserController.java +++ b/src/main/java/com/kuit/kuit4serverauth/controller/UserController.java @@ -1,23 +1,50 @@ package com.kuit.kuit4serverauth.controller; -import jakarta.servlet.http.HttpServletRequest; +import com.kuit.kuit4serverauth.dto.OrderInfoDTO; +import com.kuit.kuit4serverauth.dto.UserInfo; +import com.kuit.kuit4serverauth.model.Store; +import com.kuit.kuit4serverauth.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController +@Slf4j +@RequiredArgsConstructor public class UserController { + private final UserService userService; + @GetMapping("/profile") - public ResponseEntity getProfile(HttpServletRequest request) { - // TODO : 로그인 한 사용자면 username 이용해 "Hello, {username}" 반환하기 + public ResponseEntity getProfile(UserInfo userInfo) { + if (userInfo.getUsername() != null) { + return ResponseEntity.ok("Hello, " + userInfo.getUsername()); + } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized"); } @GetMapping("/admin") - public ResponseEntity getAdmin(HttpServletRequest request) { - // TODO: role이 admin이면 "Hello, admin" 반환하기 + public ResponseEntity getAdmin(UserInfo userInfo) { + if (userInfo.getRole() != null && userInfo.getRole().equals("ROLE_ADMIN")) { + return ResponseEntity.ok("Hello, admin"); + } return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Forbidden"); } + + @GetMapping("/users/{userId}/orders") + public ResponseEntity> getUsersAllOrders(@PathVariable Long userId) { + List usersAllOrders = userService.findUsersAllOrders(userId); + return ResponseEntity.ok(usersAllOrders); + } + + @GetMapping("/users/{userId}/ordered-stores") + public ResponseEntity> getStoresWithMultipleOrders(@PathVariable Long userId, + @RequestParam("minOrderCount") int minOrderCount) { + List stores = userService.findStoresWithMultipleOrders(userId, minOrderCount); + return ResponseEntity.ok(stores); + } } diff --git a/src/main/java/com/kuit/kuit4serverauth/dto/LoginRequest.java b/src/main/java/com/kuit/kuit4serverauth/dto/LoginRequest.java new file mode 100644 index 0000000..4a5b289 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/dto/LoginRequest.java @@ -0,0 +1,12 @@ +package com.kuit.kuit4serverauth.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LoginRequest { + + private String username; + private String password; +} diff --git a/src/main/java/com/kuit/kuit4serverauth/dto/MenuStoreDTO.java b/src/main/java/com/kuit/kuit4serverauth/dto/MenuStoreDTO.java new file mode 100644 index 0000000..09ddb34 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/dto/MenuStoreDTO.java @@ -0,0 +1,15 @@ +package com.kuit.kuit4serverauth.dto; + +import com.kuit.kuit4serverauth.model.Menu; +import com.kuit.kuit4serverauth.model.Store; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class MenuStoreDTO { + private Store store; + private Menu menu; +} diff --git a/src/main/java/com/kuit/kuit4serverauth/dto/OrderInfoDTO.java b/src/main/java/com/kuit/kuit4serverauth/dto/OrderInfoDTO.java new file mode 100644 index 0000000..95f34cb --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/dto/OrderInfoDTO.java @@ -0,0 +1,18 @@ +package com.kuit.kuit4serverauth.dto; + +import com.kuit.kuit4serverauth.model.Order; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class OrderInfoDTO { + + Order order; + int totalPrice; + String storeName; + String menuName; + +} diff --git a/src/main/java/com/kuit/kuit4serverauth/dto/StoreCategoryOrderCountDTO.java b/src/main/java/com/kuit/kuit4serverauth/dto/StoreCategoryOrderCountDTO.java new file mode 100644 index 0000000..2b0dcf9 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/dto/StoreCategoryOrderCountDTO.java @@ -0,0 +1,18 @@ +package com.kuit.kuit4serverauth.dto; + +import com.kuit.kuit4serverauth.model.Category; +import com.kuit.kuit4serverauth.model.Store; +import lombok.*; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StoreCategoryOrderCountDTO { + + Store store; + Category category; + int orderCount; + +} diff --git a/src/main/java/com/kuit/kuit4serverauth/dto/TokenDTO.java b/src/main/java/com/kuit/kuit4serverauth/dto/TokenDTO.java new file mode 100644 index 0000000..020b373 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/dto/TokenDTO.java @@ -0,0 +1,12 @@ +package com.kuit.kuit4serverauth.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class TokenDTO { + + private final String refreshToken; + private final String accessToken; +} diff --git a/src/main/java/com/kuit/kuit4serverauth/dto/UserInfo.java b/src/main/java/com/kuit/kuit4serverauth/dto/UserInfo.java new file mode 100644 index 0000000..c046773 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/dto/UserInfo.java @@ -0,0 +1,13 @@ +package com.kuit.kuit4serverauth.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@RequiredArgsConstructor +public class UserInfo { + + private final String username; + private final String role; +} diff --git a/src/main/java/com/kuit/kuit4serverauth/exception/ErrorCode.java b/src/main/java/com/kuit/kuit4serverauth/exception/ErrorCode.java index ed89dd3..7184c8d 100644 --- a/src/main/java/com/kuit/kuit4serverauth/exception/ErrorCode.java +++ b/src/main/java/com/kuit/kuit4serverauth/exception/ErrorCode.java @@ -3,6 +3,7 @@ public enum ErrorCode { INVALID_USERNAME_OR_PASSWORD(401, "Invalid username or password"), INVALID_TOKEN(401, "Invalid or expired token"), + INVALID_REFRESH_TOKEN(401, "Invalid or expired refresh token"), MISSING_AUTH_HEADER(401, "Missing or invalid Authorization header"), FORBIDDEN_ACCESS(403, "Access denied"), INTERNAL_SERVER_ERROR(500, "Internal server error"); diff --git a/src/main/java/com/kuit/kuit4serverauth/model/Category.java b/src/main/java/com/kuit/kuit4serverauth/model/Category.java new file mode 100644 index 0000000..81b543f --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/model/Category.java @@ -0,0 +1,14 @@ +package com.kuit.kuit4serverauth.model; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Category { + + private Long category_id; + private String category_name; +} diff --git a/src/main/java/com/kuit/kuit4serverauth/model/Menu.java b/src/main/java/com/kuit/kuit4serverauth/model/Menu.java new file mode 100644 index 0000000..b215b9e --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/model/Menu.java @@ -0,0 +1,15 @@ +package com.kuit.kuit4serverauth.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class Menu { + private Long menu_id; + private String name; + private int price; + private Long store_id; // JPA 였다면 Store store 로 매핑했을 듯 +} diff --git a/src/main/java/com/kuit/kuit4serverauth/model/Order.java b/src/main/java/com/kuit/kuit4serverauth/model/Order.java new file mode 100644 index 0000000..8702120 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/model/Order.java @@ -0,0 +1,26 @@ +package com.kuit.kuit4serverauth.model; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Order { + private Long order_id; + private int total_price; + private Long store_id; + private Long menu_id; + private Long user_id; + + // JPA 였다면 + // Store store + // Menu menu + // User user + + // 혹은 + // Store store + // List orderMenuList + // User user +} diff --git a/src/main/java/com/kuit/kuit4serverauth/model/Store.java b/src/main/java/com/kuit/kuit4serverauth/model/Store.java new file mode 100644 index 0000000..744248a --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/model/Store.java @@ -0,0 +1,19 @@ +package com.kuit.kuit4serverauth.model; + +import lombok.*; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Store { + private Long store_id; + private String name; + private Integer minimum_order_price; + private String status; + private Long category_id; + + // JPA 였다면 + // Category category +} diff --git a/src/main/java/com/kuit/kuit4serverauth/model/User.java b/src/main/java/com/kuit/kuit4serverauth/model/User.java index 7160884..2203442 100644 --- a/src/main/java/com/kuit/kuit4serverauth/model/User.java +++ b/src/main/java/com/kuit/kuit4serverauth/model/User.java @@ -1,10 +1,12 @@ package com.kuit.kuit4serverauth.model; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class User { private Long id; private String username; diff --git a/src/main/java/com/kuit/kuit4serverauth/repository/CategoryRepository.java b/src/main/java/com/kuit/kuit4serverauth/repository/CategoryRepository.java new file mode 100644 index 0000000..eb3f22e --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/repository/CategoryRepository.java @@ -0,0 +1,51 @@ +package com.kuit.kuit4serverauth.repository; + +import com.kuit.kuit4serverauth.dto.StoreCategoryOrderCountDTO; +import com.kuit.kuit4serverauth.model.Category; +import com.kuit.kuit4serverauth.model.Store; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class CategoryRepository { + + private final JdbcTemplate jdbcTemplate; + + public CategoryRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + // 특정 카테고리에서 주문이 가장 많은 상위 음식점 조회. + public List findMostOrderedStoreByCategory(Long category_id) { + String sql = "SELECT s.store_id, s.name, s.category_id, s.minimum_order_price, s.status, COUNT(o.order_id) AS orderCount, c.category_id, c.category_name " + + "FROM stores s " + + "JOIN orders o on s.store_id = o.store_id " + + "JOIN categories c on s.category_id = c.category_id " + + "WHERE s.category_id = ? " + + "GROUP BY s.store_id, s.name, s.category_id, s.minimum_order_price, s.status, c.category_id, c.category_name " + + "ORDER BY orderCount DESC"; + + return jdbcTemplate.query(sql, (rs, rowNum) -> { + Store store = Store.builder() + .store_id(rs.getLong("store_id")) + .name(rs.getString("name")) + .category_id(rs.getLong("category_id")) + .minimum_order_price(rs.getInt("minimum_order_price")) + .status(rs.getString("status")) + .build(); + + Category category = Category.builder() + .category_id(rs.getLong("category_id")) + .category_name(rs.getString("category_name")) + .build(); + + return StoreCategoryOrderCountDTO.builder() + .store(store) + .category(category) + .orderCount(rs.getInt("orderCount")) + .build(); + }, category_id); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/repository/MenuRepository.java b/src/main/java/com/kuit/kuit4serverauth/repository/MenuRepository.java new file mode 100644 index 0000000..869cb94 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/repository/MenuRepository.java @@ -0,0 +1,50 @@ +package com.kuit.kuit4serverauth.repository; + +import com.kuit.kuit4serverauth.dto.MenuStoreDTO; +import com.kuit.kuit4serverauth.model.Menu; +import com.kuit.kuit4serverauth.model.Store; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class MenuRepository { + + private final JdbcTemplate jdbcTemplate; + + public MenuRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + // 메뉴 이름에 특정 문자열이 포함된 메뉴와 해당 음식점을 조회. + public List findMenuAndStoreNameByMenuName(String menuName){ + String sql = "SELECT s.store_id, s.name, s.minimum_order_price, s.status, s.category_id, m.menu_id, m.name AS menuName, m.price, m.store_id " + + "FROM menus m " + + "JOIN stores s ON s.store_id = m.store_id " + + "JOIN categories c ON s.category_id = c.category_id " + + "WHERE m.name LIKE ?"; + + return jdbcTemplate.query(sql, (rs, rowNum) -> { + Store store = Store.builder() + .store_id(rs.getLong("store_id")) + .name(rs.getString("name")) + .category_id(rs.getLong("category_id")) + .minimum_order_price(rs.getInt("minimum_order_price")) + .status(rs.getString("status")) + .build(); + + Menu menu = Menu.builder() + .menu_id(rs.getLong("menu_id")) + .name(rs.getString("menuName")) + .price(rs.getInt("price")) + .store_id(rs.getLong("store_id")) + .build(); + + return MenuStoreDTO.builder() + .store(store) + .menu(menu) + .build(); + }, "%"+menuName+"%"); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/repository/RefreshTokenRepository.java b/src/main/java/com/kuit/kuit4serverauth/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..c064589 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/repository/RefreshTokenRepository.java @@ -0,0 +1,31 @@ +package com.kuit.kuit4serverauth.repository; + +import com.kuit.kuit4serverauth.model.User; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class RefreshTokenRepository { + + private final JdbcTemplate jdbcTemplate; + + public RefreshTokenRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public String findByUsername(String username) { + String sql = "SELECT refreshToken FROM refreshTokens WHERE username = ?"; + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getString("refreshToken"), username); + } + + public void save(String username, String refreshToken) { + String sql = "INSERT INTO refreshTokens (username, refreshToken) VALUES (?, ?)"; + jdbcTemplate.update(sql, username, refreshToken); + } + + public void updateRefreshToken(String username, String refreshToken) { + String sql = "UPDATE refreshTokens SET refreshToken = ? WHERE username = ?"; + jdbcTemplate.update(sql, refreshToken, username); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/repository/StoreRepository.java b/src/main/java/com/kuit/kuit4serverauth/repository/StoreRepository.java new file mode 100644 index 0000000..bc05d45 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/repository/StoreRepository.java @@ -0,0 +1,26 @@ +package com.kuit.kuit4serverauth.repository; + +import com.kuit.kuit4serverauth.model.Store; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public class StoreRepository { + + private final JdbcTemplate jdbcTemplate; + + public StoreRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + // 최소 주문 금액 조건과 활성 상태(status) 조건을 만족하는 음식점 조회. + public List findByMinimumOrderPriceAndStatus(Integer minimumOrderPrice, String status){ + String sql = "SELECT * FROM stores s WHERE s.minimum_order_price >= ? AND s.status = ?"; + return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Store.class), minimumOrderPrice, status); + } + + +} diff --git a/src/main/java/com/kuit/kuit4serverauth/repository/UserRepository.java b/src/main/java/com/kuit/kuit4serverauth/repository/UserRepository.java index 8927fee..bc8412f 100644 --- a/src/main/java/com/kuit/kuit4serverauth/repository/UserRepository.java +++ b/src/main/java/com/kuit/kuit4serverauth/repository/UserRepository.java @@ -1,10 +1,13 @@ package com.kuit.kuit4serverauth.repository; -import com.kuit.kuit4serverauth.model.User; +import com.kuit.kuit4serverauth.dto.OrderInfoDTO; +import com.kuit.kuit4serverauth.model.*; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public class UserRepository { private final JdbcTemplate jdbcTemplate; @@ -17,4 +20,50 @@ public User findByUsername(String username) { String sql = "SELECT * FROM users WHERE username = ?"; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username); } + + // 특정 회원이 두 번 이상 주문한 음식점 조회 (중복 제거). + public List findStoresWithMultipleOrders(Long userId, int minOrderCount) { + String sql = "SELECT distinct s.store_id, s.name, s.category_id, s.minimum_order_price, s.status " + + "FROM stores s JOIN orders o ON s.store_id = o.store_id " + + "WHERE o.user_id = ? " + + "GROUP BY s.store_id " + + "HAVING COUNT(o.user_id) >= ?"; + + return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Store.class), userId, minOrderCount); + } + + public List findUsersAllOrders(Long userId) { + String sql = "SELECT o.order_id, o.total_price , o.store_id, o.user_id, SUM(o.total_price) AS total_sum, m.menu_id, " + + "s.name AS storeName, " + + "m.name AS menuName " + + "FROM orders o " + + "JOIN users u ON o.user_id = u.id " + + "JOIN stores s ON o.store_id = s.store_id " + + "JOIN menus m ON o.menu_id = m.menu_id " + + "WHERE o.user_id = ? " + + "GROUP BY o.order_id, o.total_price, o.store_id, o.user_id, m.menu_id, s.name, m.name " + + "ORDER BY o.total_price DESC"; + + + return jdbcTemplate.query(sql, new Object[]{userId}, (rs, rowNum) -> { + Order order = Order.builder() + .order_id(rs.getLong("order_id")) + .total_price(rs.getInt("total_price")) + .store_id(rs.getLong("store_id")) + .menu_id(rs.getLong("menu_id")) + .user_id(rs.getLong("user_id")) + .build(); + + OrderInfoDTO dto = OrderInfoDTO.builder() + .order(order) + .totalPrice(rs.getInt("total_sum")) + .storeName(rs.getString("storeName")) + .menuName(rs.getString("menuName")) + .build(); + return dto; // Return the DTO + }); + } + + + } diff --git a/src/main/java/com/kuit/kuit4serverauth/service/AuthService.java b/src/main/java/com/kuit/kuit4serverauth/service/AuthService.java new file mode 100644 index 0000000..d46e996 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/service/AuthService.java @@ -0,0 +1,66 @@ +package com.kuit.kuit4serverauth.service; + +import com.kuit.kuit4serverauth.dto.LoginRequest; +import com.kuit.kuit4serverauth.dto.TokenDTO; +import com.kuit.kuit4serverauth.exception.CustomException; +import com.kuit.kuit4serverauth.exception.ErrorCode; +import com.kuit.kuit4serverauth.model.User; +import com.kuit.kuit4serverauth.repository.RefreshTokenRepository; +import com.kuit.kuit4serverauth.repository.UserRepository; +import io.jsonwebtoken.Claims; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AuthService { + + private final JwtUtil jwtUtil; + private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; + + + public TokenDTO login(LoginRequest loginRequest) { + String username = loginRequest.getUsername(); + String password = loginRequest.getPassword(); + + User user = userRepository.findByUsername(username); + if (user == null || !user.getPassword().equals(password)) { + throw new CustomException(ErrorCode.INVALID_USERNAME_OR_PASSWORD); + } + + String accessToken = jwtUtil.generateToken(user.getUsername(), user.getRole()); + String refreshToken = jwtUtil.generateRefreshToken(user.getUsername()); + + refreshTokenRepository.save(username, refreshToken); + + return new TokenDTO(accessToken, refreshToken); + } + + public TokenDTO refresh(@RequestBody TokenDTO tokenRequest) { + String refreshToken = tokenRequest.getRefreshToken(); + + Claims claims = jwtUtil.validateToken(refreshToken); + String username = claims.getSubject(); + + String oldRefreshToken = refreshTokenRepository.findByUsername(username); + + if(!oldRefreshToken.equals(refreshToken)) { + throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN); + } + + String newAccessToken = jwtUtil.generateToken(username, (String)claims.get("role")); + + //Refresh-Token-Rotation + String newRefreshToken = jwtUtil.generateRefreshToken(username); + refreshTokenRepository.updateRefreshToken(username, newRefreshToken); + + + return new TokenDTO(newAccessToken, newRefreshToken); + } + + +} diff --git a/src/main/java/com/kuit/kuit4serverauth/service/CategoryService.java b/src/main/java/com/kuit/kuit4serverauth/service/CategoryService.java new file mode 100644 index 0000000..49c5731 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/service/CategoryService.java @@ -0,0 +1,20 @@ +package com.kuit.kuit4serverauth.service; + +import com.kuit.kuit4serverauth.dto.StoreCategoryOrderCountDTO; +import com.kuit.kuit4serverauth.repository.CategoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CategoryService { + + private final CategoryRepository categoryRepository; + + public List findMostOrderedStoreByCategory(Long category_id) { + return categoryRepository.findMostOrderedStoreByCategory(category_id); + } + +} diff --git a/src/main/java/com/kuit/kuit4serverauth/service/JwtUtil.java b/src/main/java/com/kuit/kuit4serverauth/service/JwtUtil.java index ead240e..afa3f37 100644 --- a/src/main/java/com/kuit/kuit4serverauth/service/JwtUtil.java +++ b/src/main/java/com/kuit/kuit4serverauth/service/JwtUtil.java @@ -5,14 +5,21 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; @Component public class JwtUtil { - private final String secret = "mysecretkey"; - private final long expirationMs = 3600000; // 1 hour + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private long expirationMs; // 1 hour + + @Value("${jwt.refresh-expiration}") + private long refreshExpirationMs; public String generateToken(String username, String role) { return Jwts.builder() @@ -24,10 +31,19 @@ public String generateToken(String username, String role) { .compact(); } + public String generateRefreshToken(String username) { + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + refreshExpirationMs)) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + public Claims validateToken(String token) { try { - return Jwts.parser() - .setSigningKey(secret) + return Jwts.parser() // jwt 타입인지 + .setSigningKey(secret) // 내가 발급한 게 맞는지 .parseClaimsJws(token) .getBody(); } catch (Exception e) { diff --git a/src/main/java/com/kuit/kuit4serverauth/service/MenuService.java b/src/main/java/com/kuit/kuit4serverauth/service/MenuService.java new file mode 100644 index 0000000..7a6e1bf --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/service/MenuService.java @@ -0,0 +1,19 @@ +package com.kuit.kuit4serverauth.service; + +import com.kuit.kuit4serverauth.dto.MenuStoreDTO; +import com.kuit.kuit4serverauth.repository.MenuRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class MenuService { + + private final MenuRepository menuRepository; + + public List findMenuAndStoreNameByMenuName(String menuName) { + return menuRepository.findMenuAndStoreNameByMenuName(menuName); + } +} diff --git a/src/main/java/com/kuit/kuit4serverauth/service/StoreService.java b/src/main/java/com/kuit/kuit4serverauth/service/StoreService.java new file mode 100644 index 0000000..684d6d1 --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/service/StoreService.java @@ -0,0 +1,20 @@ +package com.kuit.kuit4serverauth.service; + +import com.kuit.kuit4serverauth.model.Store; +import com.kuit.kuit4serverauth.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class StoreService { + + private final StoreRepository storeRepository; + + public List findStoresByConditions(Integer minimumOrderPrice, String status){ + return storeRepository.findByMinimumOrderPriceAndStatus(minimumOrderPrice, status); + } + +} diff --git a/src/main/java/com/kuit/kuit4serverauth/service/UserService.java b/src/main/java/com/kuit/kuit4serverauth/service/UserService.java new file mode 100644 index 0000000..a89147b --- /dev/null +++ b/src/main/java/com/kuit/kuit4serverauth/service/UserService.java @@ -0,0 +1,24 @@ +package com.kuit.kuit4serverauth.service; + +import com.kuit.kuit4serverauth.dto.OrderInfoDTO; +import com.kuit.kuit4serverauth.model.Store; +import com.kuit.kuit4serverauth.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + + public List findUsersAllOrders(Long userId) { + return userRepository.findUsersAllOrders(userId); + } + + public List findStoresWithMultipleOrders(Long userId, int minOrderCount) { + return userRepository.findStoresWithMultipleOrders(userId, minOrderCount); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 59bd888..4c8ec7f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,16 +3,14 @@ server: spring: datasource: - url: jdbc:h2:mem:mission_db;DB_CLOSE_DELAY=-1;MODE=MYSQL - driver-class-name: org.h2.Driver - username: sa - password: password - h2: - console: - enabled: true - path: /h2-console + url: jdbc:mysql://localhost:3306/kuit_week9?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + driver-class-name: com.mysql.cj.jdbc.Driver + username: ksg1227 + password: skcjswo00 jwt: - secret: mysecretkey - expiration: 3600000 + secret: mysecretkey01234567890123456789012345678901234 + expiration: 100000 + refresh-expiration: 60480000 + diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 33ae371..35a7310 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,2 +1,33 @@ INSERT INTO users (username, password, role) VALUES ('admin', 'admin123', 'ROLE_ADMIN'); INSERT INTO users (username, password, role) VALUES ('user', 'user123', 'ROLE_USER'); +INSERT INTO categories (category_name) VALUES ('분식'); +INSERT INTO categories (category_name) VALUES ('중식'); +INSERT INTO categories (category_name) VALUES ('한식'); +INSERT INTO stores (name, minimum_order_price, status, category_id) VALUES ('교촌치킨', 10000, 'activated', 1); +INSERT INTO stores (name, minimum_order_price, status, category_id) VALUES ('bbq치킨', 9000, 'activated', 1); +INSERT INTO stores (name, minimum_order_price, status, category_id) VALUES ('bhc치킨', 12000, 'deactivated', 1); +INSERT INTO stores (name, minimum_order_price, status, category_id) VALUES ('중경마라', 12000, 'deactivated', 2); +INSERT INTO stores (name, minimum_order_price, status, category_id) VALUES ('홍콩반점', 6000, 'deactivated', 2); +INSERT INTO menus (name, price, store_id) VALUES ('떡볶이1', 5000, 1); +INSERT INTO menus (name, price, store_id) VALUES ('떡볶이2', 3000, 2); +INSERT INTO menus (name, price, store_id) VALUES ('떡볶이3', 3000, 3); +INSERT INTO menus (name, price, store_id) VALUES ('짜장면', 7000, 4); +INSERT INTO menus (name, price, store_id) VALUES ('짜장면', 7000, 5); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (10000, 1, 1, 1); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (20000, 2, 2, 1); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 3, 3, 1); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (40000, 1, 1, 1); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (40000, 2, 2, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (40000, 2, 2, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (40000, 3, 3, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (10000, 2, 2, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 2, 2, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (60000, 3, 3, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 4, 4, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 4, 4, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 4, 4, 2); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 5, 5, 1); +INSERT INTO orders (total_price, store_id, menu_id, user_id) VALUES (30000, 5, 5, 1); + + + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index c604313..42af746 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -3,4 +3,44 @@ CREATE TABLE users ( username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL -); \ No newline at end of file +); + +CREATE TABLE refreshTokens ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + refreshToken VARCHAR(255) NOT NULL +); + +CREATE TABLE categories ( + category_id BIGINT AUTO_INCREMENT PRIMARY KEY, + category_name VARCHAR(50) NOT NULL +); + +CREATE TABLE stores ( + store_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + minimum_order_price INTEGER, + status VARCHAR(50) NOT NULL, + category_id BIGINT, + FOREIGN KEY (category_id) REFERENCES categories(category_id) +); + +CREATE TABLE menus ( + menu_id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + price INT default 0, + store_id BIGINT, + FOREIGN KEY (store_id) REFERENCES stores(store_id) +); + +CREATE TABLE orders ( + order_id BIGINT AUTO_INCREMENT PRIMARY KEY, + total_price INT default 0, + store_id BIGINT, + menu_id BIGINT, + user_id BIGINT, + FOREIGN KEY (store_id) REFERENCES stores(store_id), + FOREIGN KEY (menu_id) REFERENCES menus(menu_id), + FOREIGN KEY (user_id) REFERENCES users(id) +); +