diff --git a/posttest/Docker-Compose.yml b/posttest/Docker-Compose.yml new file mode 100644 index 00000000..51590c15 --- /dev/null +++ b/posttest/Docker-Compose.yml @@ -0,0 +1,14 @@ +version: '3' +services: + app: + build: . + ports: + - "8888:8888" + environment: + - DATABASE_URL=jdbc:postgresql://localhost:5432/lottery + db: + image: postgres:latest + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + - POSTGRES_DB=lottery \ No newline at end of file diff --git a/posttest/Dockerfile b/posttest/Dockerfile new file mode 100644 index 00000000..4f3a49d3 --- /dev/null +++ b/posttest/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:21 +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/posttest/build.gradle b/posttest/build.gradle index 6d9dbbe2..4cf58097 100644 --- a/posttest/build.gradle +++ b/posttest/build.gradle @@ -2,13 +2,15 @@ plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.1.4' + id 'org.hibernate.orm' version '6.4.1.Final' + id 'org.graalvm.buildtools.native' version '0.9.28' } group = 'com.kbtg.bootcamp' -version = '0.0.1-SNAPSHOT' +version = '0.0.1' java { - sourceCompatibility = '17' + sourceCompatibility = '21' } configurations { @@ -24,12 +26,26 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.postgresql:postgresql' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.hibernate.validator:hibernate-validator' + implementation 'jakarta.validation:jakarta.validation-api' + implementation 'org.apache.commons:commons-lang3' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'org.postgresql:postgresql' compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' } tasks.named('test') { useJUnitPlatform() } + +hibernate { + enhancement { + enableAssociationManagement = true + } +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/Respone/LotteryResponse.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/Respone/LotteryResponse.java new file mode 100644 index 00000000..83f81746 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/Respone/LotteryResponse.java @@ -0,0 +1,8 @@ +package com.kbtg.bootcamp.posttest.lottery.Respone; + +import java.util.List; + +public record LotteryResponse( + List tickets +) { +} \ No newline at end of file diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/annotaion/TenDigitId.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/annotaion/TenDigitId.java new file mode 100644 index 00000000..2c1f28b1 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/annotaion/TenDigitId.java @@ -0,0 +1,17 @@ +package com.kbtg.bootcamp.posttest.lottery.annotaion; + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = TenDigitIdValidator.class) +@Target( {ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface TenDigitId { + String message() default "must be a 10-digit number"; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/annotaion/TenDigitIdValidator.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/annotaion/TenDigitIdValidator.java new file mode 100644 index 00000000..f75cb253 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/annotaion/TenDigitIdValidator.java @@ -0,0 +1,11 @@ +package com.kbtg.bootcamp.posttest.lottery.annotaion; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class TenDigitIdValidator implements ConstraintValidator { + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return value != null && value.matches("\\d{10}"); + } +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/configuration/SecurityConfiguration.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/configuration/SecurityConfiguration.java new file mode 100644 index 00000000..fe1c2f38 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/configuration/SecurityConfiguration.java @@ -0,0 +1,47 @@ +package com.kbtg.bootcamp.posttest.lottery.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + @Bean + public static PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((authorize) -> authorize + .requestMatchers("/admin/**").authenticated() + .anyRequest().permitAll() + ) + .csrf(AbstractHttpConfigurer::disable) + .httpBasic(Customizer.withDefaults()); + + return http.build(); + } + + @Bean + public UserDetailsService userDetailsService() { + UserDetails admin = User.builder().username("admin") + .password(passwordEncoder().encode("password")) + .roles("ADMIN") + .build(); + return new InMemoryUserDetailsManager(admin); + } +} \ No newline at end of file diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/AdminController.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/AdminController.java new file mode 100644 index 00000000..3ea6ef48 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/AdminController.java @@ -0,0 +1,24 @@ +package com.kbtg.bootcamp.posttest.lottery.controller; + + +import com.kbtg.bootcamp.posttest.lottery.request.AdminLotteryRequest; +import com.kbtg.bootcamp.posttest.lottery.service.LotteryService; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/admin/lotteries") +@RequiredArgsConstructor +public class AdminController { + + private final LotteryService lotteryService; + + @PostMapping + public ResponseEntity addLottery(@RequestBody AdminLotteryRequest lotteryRequest) { + return lotteryService.addLottery(lotteryRequest); + } +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/LotteryController.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/LotteryController.java new file mode 100644 index 00000000..1a90ea0a --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/LotteryController.java @@ -0,0 +1,23 @@ +package com.kbtg.bootcamp.posttest.lottery.controller; + + +import com.kbtg.bootcamp.posttest.lottery.Respone.LotteryResponse; +import com.kbtg.bootcamp.posttest.lottery.service.LotteryService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/lotteries") +@RequiredArgsConstructor +public class LotteryController { + + private final LotteryService lotteryService; + + @GetMapping + public ResponseEntity getAllLotteryTickets() { + return lotteryService.getAllLotteries(); + } +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/UserLotteryController.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/UserLotteryController.java new file mode 100644 index 00000000..a012daa4 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/controller/UserLotteryController.java @@ -0,0 +1,35 @@ +package com.kbtg.bootcamp.posttest.lottery.controller; + +import com.kbtg.bootcamp.posttest.lottery.annotaion.TenDigitId; +import com.kbtg.bootcamp.posttest.lottery.request.SellLotteryResponse; +import com.kbtg.bootcamp.posttest.lottery.service.UserTicketService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/users/{userId}/lotteries") +public class UserLotteryController { + + + private final UserTicketService userTicketService; + + @PostMapping("/{ticketId}") + public ResponseEntity buyLottery(@PathVariable @TenDigitId @Valid String userId, @PathVariable String ticketId) { + return userTicketService.buyLottery(userId, ticketId); + } + + @GetMapping + public ResponseEntity> getUserPurchasedLotteries(@PathVariable @TenDigitId @Valid String userId) { + return userTicketService.getUserLotteryTickets(userId); + } + + @DeleteMapping("/{ticketId}") + public ResponseEntity sellLottery(@PathVariable @TenDigitId @Valid String userId, @PathVariable String ticketId) { + return userTicketService.sellLotteryTicket(userId, ticketId); + } +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/entity/Lottery.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/entity/Lottery.java new file mode 100644 index 00000000..c93c275a --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/entity/Lottery.java @@ -0,0 +1,19 @@ +package com.kbtg.bootcamp.posttest.lottery.entity; + + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Data; + +@Entity +@Data +public class Lottery { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String ticket; + private int price; + private int amount; +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/entity/UserTicket.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/entity/UserTicket.java new file mode 100644 index 00000000..a99ab5a2 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/entity/UserTicket.java @@ -0,0 +1,22 @@ +package com.kbtg.bootcamp.posttest.lottery.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +public class UserTicket { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String userId; + + @ManyToOne + @JoinColumn(name = "lottery_id") + private Lottery lottery; + private int amount; + private int cost; +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/ControllerExceptionHandler.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/ControllerExceptionHandler.java new file mode 100644 index 00000000..1135693a --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/ControllerExceptionHandler.java @@ -0,0 +1,54 @@ +package com.kbtg.bootcamp.posttest.lottery.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.HandlerMethodValidationException; + +import java.util.Arrays; +import java.util.Date; + +@Slf4j +@ControllerAdvice +public class ControllerExceptionHandler { + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity resourceNotFoundException(UserNotFoundException ex, WebRequest request) { + ErrorMessage message = new ErrorMessage( + HttpStatus.NOT_FOUND.value(), + new Date(), + ex.getMessage(), + request.getDescription(false)); + + return new ResponseEntity<>(message, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity globalExceptionHandler(Exception ex, WebRequest request) { + ErrorMessage message = new ErrorMessage( + HttpStatus.INTERNAL_SERVER_ERROR.value(), + new Date(), + ex.getMessage(), + request.getDescription(false)); + + return new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + @ExceptionHandler(HandlerMethodValidationException.class) + public ErrorMessage handleValidationException(HandlerMethodValidationException ex, WebRequest request) { + String result = Arrays.toString(ex.getDetailMessageArguments()); + String message = result.replace("[", "").replace("]", ""); + + return new ErrorMessage(HttpStatus.BAD_REQUEST.value(), new Date(), message, request.getDescription(false)); + } + +} + + diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/ErrorMessage.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/ErrorMessage.java new file mode 100644 index 00000000..eeae1aa4 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/ErrorMessage.java @@ -0,0 +1,7 @@ +package com.kbtg.bootcamp.posttest.lottery.exception; + +import java.util.Date; + +public record ErrorMessage(int statusCode, Date timestamp, String message, String description) { + +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/UserNotFoundException.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/UserNotFoundException.java new file mode 100644 index 00000000..47193910 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/exception/UserNotFoundException.java @@ -0,0 +1,13 @@ +package com.kbtg.bootcamp.posttest.lottery.exception; + +import java.io.Serial; + +public class UserNotFoundException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public UserNotFoundException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/repository/LotteryRepository.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/repository/LotteryRepository.java new file mode 100644 index 00000000..6873a3f0 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/repository/LotteryRepository.java @@ -0,0 +1,12 @@ +package com.kbtg.bootcamp.posttest.lottery.repository; + +import com.kbtg.bootcamp.posttest.lottery.entity.Lottery; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface LotteryRepository extends JpaRepository { + Optional findByTicket(String ticket); +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/repository/UserTicketRepository.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/repository/UserTicketRepository.java new file mode 100644 index 00000000..73aed50b --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/repository/UserTicketRepository.java @@ -0,0 +1,17 @@ +package com.kbtg.bootcamp.posttest.lottery.repository; + +import com.kbtg.bootcamp.posttest.lottery.entity.UserTicket; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface UserTicketRepository extends JpaRepository { + List findByUserId(String userId); + + List findByUserIdAndLotteryTicket(String userId, String ticketId); + + void deleteUserTicketByUserIdAndLotteryTicket(String userId, String ticketId); + +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/request/AdminLotteryRequest.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/request/AdminLotteryRequest.java new file mode 100644 index 00000000..3b9d5213 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/request/AdminLotteryRequest.java @@ -0,0 +1,17 @@ +package com.kbtg.bootcamp.posttest.lottery.request; + +import com.kbtg.bootcamp.posttest.lottery.entity.Lottery; +import jakarta.validation.constraints.Min; + +public record AdminLotteryRequest(String ticket, + @Min(value = 1,message = "The number of bags must be greater than 0") int price, + @Min(value = 1,message = "The number of bags must be greater than 0") int amount) { + + public Lottery toLottery() { + Lottery lottery = new Lottery(); + lottery.setTicket(ticket); + lottery.setPrice(price); + lottery.setAmount(amount); + return lottery; + } +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/request/SellLotteryResponse.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/request/SellLotteryResponse.java new file mode 100644 index 00000000..d41ff136 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/request/SellLotteryResponse.java @@ -0,0 +1,6 @@ +package com.kbtg.bootcamp.posttest.lottery.request; + +public record SellLotteryResponse( + String ticket +) { +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/LotteryService.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/LotteryService.java new file mode 100644 index 00000000..2ed64dce --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/LotteryService.java @@ -0,0 +1,13 @@ +package com.kbtg.bootcamp.posttest.lottery.service; + +import com.kbtg.bootcamp.posttest.lottery.Respone.LotteryResponse; +import com.kbtg.bootcamp.posttest.lottery.request.AdminLotteryRequest; +import org.springframework.http.ResponseEntity; + +public interface LotteryService { + + ResponseEntity addLottery(AdminLotteryRequest lotteryRequest); + + ResponseEntity getAllLotteries(); + +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/UserTicketService.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/UserTicketService.java new file mode 100644 index 00000000..ad4240d8 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/UserTicketService.java @@ -0,0 +1,15 @@ +package com.kbtg.bootcamp.posttest.lottery.service; + +import com.kbtg.bootcamp.posttest.lottery.request.SellLotteryResponse; +import org.springframework.http.ResponseEntity; + +import java.util.Map; + +public interface UserTicketService { + + ResponseEntity buyLottery(String userId, String ticketId); + + ResponseEntity> getUserLotteryTickets(String userId); + + ResponseEntity sellLotteryTicket(String userId, String ticketId); +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/impl/LotteryServiceImpl.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/impl/LotteryServiceImpl.java new file mode 100644 index 00000000..ea74353f --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/impl/LotteryServiceImpl.java @@ -0,0 +1,40 @@ +package com.kbtg.bootcamp.posttest.lottery.service.impl; + +import com.kbtg.bootcamp.posttest.lottery.Respone.LotteryResponse; +import com.kbtg.bootcamp.posttest.lottery.entity.Lottery; +import com.kbtg.bootcamp.posttest.lottery.repository.LotteryRepository; +import com.kbtg.bootcamp.posttest.lottery.request.AdminLotteryRequest; +import com.kbtg.bootcamp.posttest.lottery.service.LotteryService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class LotteryServiceImpl implements LotteryService { + + private final LotteryRepository lotteryRepository; + + @Override + public ResponseEntity addLottery(AdminLotteryRequest lotteryRequest) { + if (lotteryRepository.findByTicket(lotteryRequest.ticket()).isPresent()) { + return ResponseEntity.badRequest().body(null); + } + + Lottery savedLottery = lotteryRepository.save(lotteryRequest.toLottery()); + return new ResponseEntity<>((savedLottery), HttpStatus.CREATED); + } + + @Override + public ResponseEntity getAllLotteries() { + List lotteries = lotteryRepository.findAll(); + List tickets = lotteries.stream().map(Lottery::getTicket).collect(Collectors.toList()); + LotteryResponse lotteryResponse = new LotteryResponse(tickets); + return ResponseEntity.ok(lotteryResponse); + } + +} diff --git a/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/impl/UserTicketServiceImpl.java b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/impl/UserTicketServiceImpl.java new file mode 100644 index 00000000..c3bf5df7 --- /dev/null +++ b/posttest/src/main/java/com/kbtg/bootcamp/posttest/lottery/service/impl/UserTicketServiceImpl.java @@ -0,0 +1,80 @@ +package com.kbtg.bootcamp.posttest.lottery.service.impl; + +import com.kbtg.bootcamp.posttest.lottery.entity.Lottery; +import com.kbtg.bootcamp.posttest.lottery.entity.UserTicket; +import com.kbtg.bootcamp.posttest.lottery.repository.LotteryRepository; +import com.kbtg.bootcamp.posttest.lottery.repository.UserTicketRepository; +import com.kbtg.bootcamp.posttest.lottery.request.SellLotteryResponse; +import com.kbtg.bootcamp.posttest.lottery.service.UserTicketService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class UserTicketServiceImpl implements UserTicketService { + + private final UserTicketRepository userTicketRepository; + + private final LotteryRepository lotteryRepository; + + @Override + public ResponseEntity buyLottery(String userId, String ticketId) { + + Optional lotteryOptional = lotteryRepository.findByTicket(ticketId); + if (lotteryOptional.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + UserTicket userTicket = new UserTicket(); + userTicket.setUserId(userId); + userTicket.setLottery(lotteryOptional.get()); + userTicket.setAmount(1); + userTicket.setCost(lotteryOptional.get().getPrice()); + UserTicket savedUserTicket = userTicketRepository.save(userTicket); + return ResponseEntity.status(HttpStatus.CREATED).body(savedUserTicket); + } + + @Override + public ResponseEntity> getUserLotteryTickets(String userId) { + List userTickets = userTicketRepository.findByUserId(userId); + + int totalCost = userTickets.stream() + .mapToInt(ut -> ut.getLottery().getPrice() * ut.getAmount()) + .sum(); + + int totalTickets = userTickets.size(); + + List ticketNumbers = userTickets.stream() + .map(ut -> ut.getLottery().getTicket()) + .collect(Collectors.toList()); + + Map result = new HashMap<>(); + result.put("tickets", ticketNumbers); + result.put("count", totalTickets); + result.put("cost", totalCost); + return ResponseEntity.ok(result); + } + + @Transactional + @Override + public ResponseEntity sellLotteryTicket(String userId, String ticketId) { + List userTickets = userTicketRepository.findByUserIdAndLotteryTicket(userId, ticketId); + + if (userTickets.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + userTicketRepository.deleteUserTicketByUserIdAndLotteryTicket(userId, ticketId); + + return ResponseEntity.ok(new SellLotteryResponse(ticketId)); + } +} diff --git a/posttest/src/main/resources/application.properties b/posttest/src/main/resources/application.properties index 8d3eb44e..17ff18c4 100644 --- a/posttest/src/main/resources/application.properties +++ b/posttest/src/main/resources/application.properties @@ -1,6 +1,6 @@ -server.port=8081 -spring.datasource.url=jdbc:postgresql://localhost:5432/ -spring.datasource.username= -spring.datasource.password= -spring.jpa.hibernate.ddl-auto= -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file +server.port=8888 +spring.datasource.url=jdbc:postgresql://localhost:5432/lottery +spring.datasource.username=postgres +spring.datasource.password=1234 +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file diff --git a/posttest/src/test/java/com/kbtg/bootcamp/posttest/lottery/service/impl/LotteryServiceImplTest.java b/posttest/src/test/java/com/kbtg/bootcamp/posttest/lottery/service/impl/LotteryServiceImplTest.java new file mode 100644 index 00000000..c85acaba --- /dev/null +++ b/posttest/src/test/java/com/kbtg/bootcamp/posttest/lottery/service/impl/LotteryServiceImplTest.java @@ -0,0 +1,70 @@ +package com.kbtg.bootcamp.posttest.lottery.service.impl; + +import com.kbtg.bootcamp.posttest.lottery.Respone.LotteryResponse; +import com.kbtg.bootcamp.posttest.lottery.entity.Lottery; +import com.kbtg.bootcamp.posttest.lottery.repository.LotteryRepository; +import com.kbtg.bootcamp.posttest.lottery.request.AdminLotteryRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class LotteryServiceImplTest { + + @Mock + private LotteryRepository lotteryRepository; + + @InjectMocks + private LotteryServiceImpl lotteryService; + + @Test + public void testAddLottery_whenLotteryExists_thenBadRequest() { + AdminLotteryRequest lotteryRequest = new AdminLotteryRequest("123456", 80, 1); + Lottery existingLottery = new Lottery(); + existingLottery.setTicket("123456"); + when(lotteryRepository.findByTicket(lotteryRequest.ticket())).thenReturn(Optional.of(existingLottery)); + + ResponseEntity response = lotteryService.addLottery(lotteryRequest); + + assertEquals(ResponseEntity.badRequest().build(), response); + } + + @Test + public void testAddLottery_whenLotteryDoesNotExist_thenCreated() { + AdminLotteryRequest lotteryRequest = new AdminLotteryRequest("123456", 80, 1); + Lottery savedLottery = new Lottery(); + savedLottery.setTicket("123456"); + when(lotteryRepository.findByTicket(lotteryRequest.ticket())).thenReturn(Optional.empty()); + when(lotteryRepository.save(lotteryRequest.toLottery())).thenReturn(savedLottery); + + ResponseEntity response = lotteryService.addLottery(lotteryRequest); + + assertEquals(ResponseEntity.status(HttpStatus.CREATED).body(savedLottery), response); + } + + @Test + public void testGetAllLotteries_whenLotteriesExist_thenOk() { + Lottery lottery1 = new Lottery(); + lottery1.setTicket("123456"); + Lottery lottery2 = new Lottery(); + lottery2.setTicket("654321"); + List lotteries = Arrays.asList(lottery1, lottery2); + when(lotteryRepository.findAll()).thenReturn(lotteries); + + ResponseEntity response = lotteryService.getAllLotteries(); + + LotteryResponse expectedResponse = new LotteryResponse(Arrays.asList(lottery1.getTicket(), lottery2.getTicket())); + assertEquals(ResponseEntity.ok(expectedResponse), response); + } +} \ No newline at end of file diff --git a/posttest/src/test/java/com/kbtg/bootcamp/posttest/lottery/service/impl/UserTicketServiceImplTest.java b/posttest/src/test/java/com/kbtg/bootcamp/posttest/lottery/service/impl/UserTicketServiceImplTest.java new file mode 100644 index 00000000..195dd796 --- /dev/null +++ b/posttest/src/test/java/com/kbtg/bootcamp/posttest/lottery/service/impl/UserTicketServiceImplTest.java @@ -0,0 +1,167 @@ +package com.kbtg.bootcamp.posttest.lottery.service.impl; + +import com.kbtg.bootcamp.posttest.lottery.entity.Lottery; +import com.kbtg.bootcamp.posttest.lottery.entity.UserTicket; +import com.kbtg.bootcamp.posttest.lottery.repository.LotteryRepository; +import com.kbtg.bootcamp.posttest.lottery.repository.UserTicketRepository; +import com.kbtg.bootcamp.posttest.lottery.request.SellLotteryResponse; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class UserTicketServiceImplTest { + + @Mock + private UserTicketRepository userTicketRepository; + + @Mock + private LotteryRepository lotteryRepository; + + @InjectMocks + private UserTicketServiceImpl userTicketService; + + @Test + public void testBuyLottery_whenLotteryDoesNotExist_thenNotFound() { + when(lotteryRepository.findByTicket(anyString())).thenReturn(Optional.empty()); + + ResponseEntity response = userTicketService.buyLottery("1234567890", "ticket123"); + + assertEquals(ResponseEntity.notFound().build(), response); + } + + @Test + public void testBuyLottery_Success() { + String userId = "user123"; + String ticketId = "ticket456"; + + Lottery lottery = new Lottery(); + lottery.setTicket(ticketId); + lottery.setPrice(10); + + when(lotteryRepository.findByTicket(ticketId)).thenReturn(Optional.of(lottery)); + + UserTicket savedUserTicket = new UserTicket(); + savedUserTicket.setUserId(userId); + savedUserTicket.setLottery(lottery); + savedUserTicket.setAmount(1); + savedUserTicket.setCost(lottery.getPrice()); + + when(userTicketRepository.save(any(UserTicket.class))).thenReturn(savedUserTicket); + + ResponseEntity response = userTicketService.buyLottery(userId, ticketId); + + verify(userTicketRepository).save(any(UserTicket.class)); + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(savedUserTicket, response.getBody()); + } + + @Test + public void testBuyLottery_NotFound() { + String userId = "user123"; + String ticketId = "nonExistentTicket"; + + when(lotteryRepository.findByTicket(ticketId)).thenReturn(Optional.empty()); + + ResponseEntity response = userTicketService.buyLottery(userId, ticketId); + + verify(userTicketRepository, never()).save(any(UserTicket.class)); + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + + @Test + public void testGetUserLotteryTickets_whenUserTicketsExist_thenOk() { + UserTicket userTicket1 = new UserTicket(); + userTicket1.setUserId("1234567890"); + Lottery lottery = new Lottery(); + lottery.setTicket("ticket123"); + lottery.setPrice(10); + userTicket1.setLottery(lottery); + userTicket1.setAmount(1); + userTicket1.setCost(10); + + UserTicket userTicket2 = new UserTicket(); + Lottery lottery2 = new Lottery(); + lottery2.setTicket("ticket456"); + lottery2.setPrice(20); + userTicket2.setUserId("1234567890"); + userTicket2.setLottery(lottery2); + userTicket2.setAmount(2); + userTicket2.setCost(20); + + when(userTicketRepository.findByUserId(anyString())).thenReturn(Arrays.asList(userTicket1, userTicket2)); + + ResponseEntity> response = userTicketService.getUserLotteryTickets("1234567890"); + + Map expectedResult = new HashMap<>(); + expectedResult.put("tickets", Arrays.asList("ticket123", "ticket456")); + expectedResult.put("count", 2); + expectedResult.put("cost", 50); + assertEquals(ResponseEntity.ok(expectedResult), response); + } + + @Test + public void testGetUserLotteryTickets_whenNoUserTicketsExist_thenOk() { + when(userTicketRepository.findByUserId(anyString())).thenReturn(Collections.emptyList()); + + ResponseEntity> response = userTicketService.getUserLotteryTickets("1234567890"); + + Map expectedResult = new HashMap<>(); + expectedResult.put("tickets", Collections.emptyList()); + expectedResult.put("count", 0); + expectedResult.put("cost", 0); + assertEquals(ResponseEntity.ok(expectedResult), response); + } + + @Test + public void testSellLotteryTicket_whenUserTicketsExist_thenOk() { + List userTickets = getUserTickets(); + + when(userTicketRepository.findByUserIdAndLotteryTicket("1234567890", "ticket123")).thenReturn(userTickets); + + ResponseEntity response = userTicketService.sellLotteryTicket("1234567890", "ticket123"); + + verify(userTicketRepository, times(1)).deleteUserTicketByUserIdAndLotteryTicket("1234567890", "ticket123"); + assertEquals(ResponseEntity.ok(new SellLotteryResponse("ticket123")), response); + } + + private static List getUserTickets() { + Lottery lottery = new Lottery(); + lottery.setTicket("ticket123"); + lottery.setPrice(10); + UserTicket userTicket1 = new UserTicket(); + userTicket1.setUserId("1234567890"); + userTicket1.setLottery(lottery); + userTicket1.setAmount(1); + userTicket1.setCost(10); + UserTicket userTicket2 = new UserTicket(); + userTicket2.setUserId("1234567890"); + userTicket2.setLottery(lottery); + userTicket2.setAmount(2); + userTicket2.setCost(10); + return Arrays.asList(userTicket1, userTicket2); + } + + @Test + public void testSellLotteryTicket_whenNoUserTicketsExist_thenNotFound() { + + lenient().when(userTicketRepository.findByUserIdAndLotteryTicket(anyString(), anyString())).thenReturn(Collections.emptyList()); + + ResponseEntity response = userTicketService.sellLotteryTicket("1234567890", "ticket123"); + + verify(userTicketRepository, never()).deleteUserTicketByUserIdAndLotteryTicket("1234567890", "ticket123"); + assertEquals(ResponseEntity.notFound().build(), response); + Mockito.validateMockitoUsage(); + } +} \ No newline at end of file