Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KBTG Lottery Assessment #27

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions posttest/Docker-Compose.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions posttest/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM openjdk:21
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
22 changes: 19 additions & 3 deletions posttest/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kbtg.bootcamp.posttest.lottery.Respone;

import java.util.List;

public record LotteryResponse(
List<String> tickets
) {
}
Original file line number Diff line number Diff line change
@@ -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<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.kbtg.bootcamp.posttest.lottery.annotaion;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class TenDigitIdValidator implements ConstraintValidator<TenDigitId, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.matches("\\d{10}");
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Object> addLottery(@RequestBody AdminLotteryRequest lotteryRequest) {
return lotteryService.addLottery(lotteryRequest);
}
}
Original file line number Diff line number Diff line change
@@ -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<LotteryResponse> getAllLotteryTickets() {
return lotteryService.getAllLotteries();
}
}
Original file line number Diff line number Diff line change
@@ -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<Object> buyLottery(@PathVariable @TenDigitId @Valid String userId, @PathVariable String ticketId) {
return userTicketService.buyLottery(userId, ticketId);
}

@GetMapping
public ResponseEntity<Map<String, Object>> getUserPurchasedLotteries(@PathVariable @TenDigitId @Valid String userId) {
return userTicketService.getUserLotteryTickets(userId);
}

@DeleteMapping("/{ticketId}")
public ResponseEntity<SellLotteryResponse> sellLottery(@PathVariable @TenDigitId @Valid String userId, @PathVariable String ticketId) {
return userTicketService.sellLotteryTicket(userId, ticketId);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<ErrorMessage> 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<ErrorMessage> 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));
}

}


Original file line number Diff line number Diff line change
@@ -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) {

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Lottery, Long> {
Optional<Lottery> findByTicket(String ticket);
}
Original file line number Diff line number Diff line change
@@ -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<UserTicket, Long> {
List<UserTicket> findByUserId(String userId);

List<UserTicket> findByUserIdAndLotteryTicket(String userId, String ticketId);

void deleteUserTicketByUserIdAndLotteryTicket(String userId, String ticketId);

}
Loading