diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..beba430 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,55 @@ +name: CD with Gradle + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: make application yml + run: | + cd ./src/main/resources + touch ./application.yml + echo "${{ secrets.APPLICATION_YML }}" > ./application.yml + shell: bash + + - name: Build with Gradle + run: | + chmod +x ./gradlew + ./gradlew clean build -x test + + - name: Docker build & push to docker repo + run: | + docker login -u ${{ secrets.DOCKER_USERNAME_YUB }} -p ${{ secrets.DOCKER_PASSWORD_YUB }} + docker build -f Dockerfile -t "${{ secrets.DOCKER_USERNAME_YUB }}/hanafun:latest" . + docker push "${{ secrets.DOCKER_USERNAME_YUB }}/hanafun:latest" + + - name: Deploy to server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ec2-user + key: ${{ secrets.EC2_SSH_KEY }} + script: | + sudo docker ps + sudo docker pull "${{ secrets.DOCKER_USERNAME_YUB }}/hanafun:latest" + sudo docker stop hanafun + sudo docker rm hanafun + sudo docker run -d -p 8080:8080 --name hanafun "${{ secrets.DOCKER_USERNAME_YUB }}/hanafun:latest" + sudo docker image prune -f \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b24d96 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM openjdk:17-jdk +LABEL maintainer="yubin" +ARG JAR_FILE=build/libs/*.jar +ADD ${JAR_FILE} app.jar +EXPOSE 8099 +ENTRYPOINT ["java","-jar","/app.jar"] diff --git a/build.gradle b/build.gradle index 043ddad..c140101 100644 --- a/build.gradle +++ b/build.gradle @@ -24,17 +24,33 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-security' + //Jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // AWS S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { useJUnitPlatform() } + +bootJar { + duplicatesStrategy = 'exclude' +} + +jar { enabled = false } \ No newline at end of file diff --git a/src/main/java/com/hanaro/hanafun/HanafunApplication.java b/src/main/java/com/hanaro/hanafun/HanafunApplication.java index 131783e..f50b9e9 100644 --- a/src/main/java/com/hanaro/hanafun/HanafunApplication.java +++ b/src/main/java/com/hanaro/hanafun/HanafunApplication.java @@ -2,8 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableJpaAuditing +@EnableScheduling public class HanafunApplication { public static void main(String[] args) { diff --git a/src/main/java/com/hanaro/hanafun/account/controller/AccountController.java b/src/main/java/com/hanaro/hanafun/account/controller/AccountController.java new file mode 100644 index 0000000..975d0b5 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/controller/AccountController.java @@ -0,0 +1,26 @@ +package com.hanaro.hanafun.account.controller; + +import com.hanaro.hanafun.account.dto.AccountResDto; +import com.hanaro.hanafun.account.service.impl.AccountServiceImpl; +import com.hanaro.hanafun.common.dto.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/account") +@RequiredArgsConstructor +public class AccountController { + private final AccountServiceImpl accountService; + + @GetMapping("/list") + ResponseEntity readAccountList(@AuthenticationPrincipal Long userId){ + List accountResDtoList = accountService.readAccountList(userId); + return ResponseEntity.ok(new ApiResponse(true, "ok", accountResDtoList)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/account/domain/AccountEntity.java b/src/main/java/com/hanaro/hanafun/account/domain/AccountEntity.java new file mode 100644 index 0000000..163cbc1 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/domain/AccountEntity.java @@ -0,0 +1,40 @@ +package com.hanaro.hanafun.account.domain; + +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "account") +@Data +@EqualsAndHashCode(callSuper = false) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AccountEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "account_id") + private Long accountId; + + @ManyToOne(optional = false) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity userEntity; + + @Column(name = "account_number", length = 20, nullable = false) + private String accountNumber; + + @Column(name = "account_name", length = 50) + private String accountName; + + @Column(name = "password", length = 20, nullable = false) + private String password; + + @Column(name = "balance", nullable = false) + private Integer balance; + + @Column(name = "qr") + private String qr; + + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted; +} diff --git a/src/main/java/com/hanaro/hanafun/account/domain/AccountRepository.java b/src/main/java/com/hanaro/hanafun/account/domain/AccountRepository.java new file mode 100644 index 0000000..6a032fd --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/domain/AccountRepository.java @@ -0,0 +1,10 @@ +package com.hanaro.hanafun.account.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface AccountRepository extends JpaRepository { + Optional> findByUserEntityUserId(Long userId); +} diff --git a/src/main/java/com/hanaro/hanafun/account/dto/AccountResDto.java b/src/main/java/com/hanaro/hanafun/account/dto/AccountResDto.java new file mode 100644 index 0000000..08c093f --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/dto/AccountResDto.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.account.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AccountResDto { + private long accountId; + private String accountName; + private String accountNumber; + private int balance; +} diff --git a/src/main/java/com/hanaro/hanafun/account/exception/AccountBalanceException.java b/src/main/java/com/hanaro/hanafun/account/exception/AccountBalanceException.java new file mode 100644 index 0000000..b4de724 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/exception/AccountBalanceException.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.account.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class AccountBalanceException extends CustomException { + static String MESSAGE = "INSUFFICIENT_BALANCE"; + public AccountBalanceException(){ + super(MESSAGE); + } + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.BAD_REQUEST; + } +} diff --git a/src/main/java/com/hanaro/hanafun/account/exception/AccountNotFoundException.java b/src/main/java/com/hanaro/hanafun/account/exception/AccountNotFoundException.java new file mode 100644 index 0000000..301990b --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/exception/AccountNotFoundException.java @@ -0,0 +1,16 @@ +package com.hanaro.hanafun.account.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class AccountNotFoundException extends CustomException { + static String MESSAGE = "ACCOUNT_NOT_FOUND"; + + public AccountNotFoundException(){ + super(MESSAGE); + } + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/account/mapper/AccountMapper.java b/src/main/java/com/hanaro/hanafun/account/mapper/AccountMapper.java new file mode 100644 index 0000000..076b431 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/mapper/AccountMapper.java @@ -0,0 +1,19 @@ +package com.hanaro.hanafun.account.mapper; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.account.dto.AccountResDto; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class AccountMapper { + public static AccountResDto entityToAccountResDto(AccountEntity accountEntity){ + if(accountEntity == null) return null; + + return AccountResDto.builder() + .accountId(accountEntity.getAccountId()) + .accountNumber(accountEntity.getAccountNumber()) + .accountName(accountEntity.getAccountName()) + .balance(accountEntity.getBalance()) + .build(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/account/service/AccountService.java b/src/main/java/com/hanaro/hanafun/account/service/AccountService.java new file mode 100644 index 0000000..4e306c6 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/service/AccountService.java @@ -0,0 +1,9 @@ +package com.hanaro.hanafun.account.service; + +import com.hanaro.hanafun.account.dto.AccountResDto; + +import java.util.List; + +public interface AccountService { + List readAccountList(Long userId); +} diff --git a/src/main/java/com/hanaro/hanafun/account/service/impl/AccountServiceImpl.java b/src/main/java/com/hanaro/hanafun/account/service/impl/AccountServiceImpl.java new file mode 100644 index 0000000..1d7fcdb --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/account/service/impl/AccountServiceImpl.java @@ -0,0 +1,28 @@ +package com.hanaro.hanafun.account.service.impl; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.account.domain.AccountRepository; +import com.hanaro.hanafun.account.dto.AccountResDto; +import com.hanaro.hanafun.account.exception.AccountNotFoundException; +import com.hanaro.hanafun.account.mapper.AccountMapper; +import com.hanaro.hanafun.account.service.AccountService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AccountServiceImpl implements AccountService { + private final AccountRepository accountRepository; + + @Override + public List readAccountList(Long userId) { + List accountEntityList = accountRepository.findByUserEntityUserId(userId).orElseThrow(() -> new AccountNotFoundException()); + if(accountEntityList.isEmpty()){ + throw new AccountNotFoundException(); + } + + return accountEntityList.stream().map(AccountMapper::entityToAccountResDto).toList(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/category/controller/CategoryController.java b/src/main/java/com/hanaro/hanafun/category/controller/CategoryController.java new file mode 100644 index 0000000..4251c1f --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/controller/CategoryController.java @@ -0,0 +1,23 @@ +package com.hanaro.hanafun.category.controller; + +import com.hanaro.hanafun.category.dto.response.CategoryResDto; +import com.hanaro.hanafun.category.service.CategoryService; +import com.hanaro.hanafun.common.dto.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class CategoryController { + private final CategoryService categoryService; + + @GetMapping("/category") + public ResponseEntity categoryList() { + List categoryResDtoList = categoryService.categoryList(); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", categoryResDtoList)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/category/domain/CategoryEntity.java b/src/main/java/com/hanaro/hanafun/category/domain/CategoryEntity.java new file mode 100644 index 0000000..d8d5de7 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/domain/CategoryEntity.java @@ -0,0 +1,20 @@ +package com.hanaro.hanafun.category.domain; + +import com.hanaro.hanafun.common.domain.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "category") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Builder +@AllArgsConstructor +public class CategoryEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long categoryId; + + @Column(nullable = false) + private String categoryName; +} diff --git a/src/main/java/com/hanaro/hanafun/category/domain/CategoryRepository.java b/src/main/java/com/hanaro/hanafun/category/domain/CategoryRepository.java new file mode 100644 index 0000000..999bf70 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/domain/CategoryRepository.java @@ -0,0 +1,9 @@ +package com.hanaro.hanafun.category.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryRepository extends JpaRepository { + +} diff --git a/src/main/java/com/hanaro/hanafun/category/dto/response/CategoryResDto.java b/src/main/java/com/hanaro/hanafun/category/dto/response/CategoryResDto.java new file mode 100644 index 0000000..e2b86b8 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/dto/response/CategoryResDto.java @@ -0,0 +1,11 @@ +package com.hanaro.hanafun.category.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CategoryResDto { + private Long categoryId; + private String categoryName; +} diff --git a/src/main/java/com/hanaro/hanafun/category/exception/CategoryNotFoundException.java b/src/main/java/com/hanaro/hanafun/category/exception/CategoryNotFoundException.java new file mode 100644 index 0000000..b053183 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/exception/CategoryNotFoundException.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.category.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class CategoryNotFoundException extends CustomException { + static String MESSAGE = "CATEGORY_NOT_FOUND"; + + public CategoryNotFoundException() { + super(MESSAGE); + } + + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/category/service/CategoryService.java b/src/main/java/com/hanaro/hanafun/category/service/CategoryService.java new file mode 100644 index 0000000..d2d1e9d --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/service/CategoryService.java @@ -0,0 +1,9 @@ +package com.hanaro.hanafun.category.service; + +import com.hanaro.hanafun.category.dto.response.CategoryResDto; + +import java.util.List; + +public interface CategoryService { + List categoryList(); +} diff --git a/src/main/java/com/hanaro/hanafun/category/service/impl/CategoryServiceImpl.java b/src/main/java/com/hanaro/hanafun/category/service/impl/CategoryServiceImpl.java new file mode 100644 index 0000000..5497f80 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/category/service/impl/CategoryServiceImpl.java @@ -0,0 +1,37 @@ +package com.hanaro.hanafun.category.service.impl; + +import com.hanaro.hanafun.category.domain.CategoryEntity; +import com.hanaro.hanafun.category.domain.CategoryRepository; +import com.hanaro.hanafun.category.dto.response.CategoryResDto; +import com.hanaro.hanafun.category.service.CategoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class CategoryServiceImpl implements CategoryService { + private final CategoryRepository categoryRepository; + + // 카테고리 전체 출력 + @Transactional + @Override + public List categoryList() { + List categoryEntities = categoryRepository.findAll(); + + List categoryResDtoList = categoryEntities.stream() + .map(categoryEntity -> { + CategoryResDto categoryResDto = CategoryResDto.builder() + .categoryId(categoryEntity.getCategoryId()) + .categoryName(categoryEntity.getCategoryName()) + .build(); + return categoryResDto; + }) + .collect(Collectors.toList()); + + return categoryResDtoList; + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/aop/GlobalExceptionHandler.java b/src/main/java/com/hanaro/hanafun/common/aop/GlobalExceptionHandler.java new file mode 100644 index 0000000..342207c --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/aop/GlobalExceptionHandler.java @@ -0,0 +1,27 @@ +package com.hanaro.hanafun.common.aop; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.common.exception.BasicErrorStatus; +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGlobalException(Exception ex) { + BasicErrorStatus error = BasicErrorStatus.INTERNAL_SERVER_ERROR; + ApiResponse response = new ApiResponse<>(false, ex.getMessage(), null); + return new ResponseEntity<>(response, error.getHttpStatus()); + } + + @ExceptionHandler(CustomException.class) + public ResponseEntity> handleCustomException (CustomException ex) { + ApiResponse response = new ApiResponse<>(false, ex.getMessage(), null); + return new ResponseEntity<>(response, ex.getHttpStatus()); + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/authentication/JwtAuthFilter.java b/src/main/java/com/hanaro/hanafun/common/authentication/JwtAuthFilter.java new file mode 100644 index 0000000..0f9d9af --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/authentication/JwtAuthFilter.java @@ -0,0 +1,40 @@ +package com.hanaro.hanafun.common.authentication; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION); + if(bearerToken == null || !bearerToken.startsWith("Bearer ")){ + chain.doFilter(request, response); + return; + } + + String token = bearerToken.split(" ")[1]; + if(jwtUtil.isExpired(token)){ + chain.doFilter(request, response); + return; + } + + SecurityContextHolder.getContext().setAuthentication(jwtUtil.getAuthFromJwt(token)); + chain.doFilter(request, response); + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/authentication/JwtUtil.java b/src/main/java/com/hanaro/hanafun/common/authentication/JwtUtil.java new file mode 100644 index 0000000..adade3a --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/authentication/JwtUtil.java @@ -0,0 +1,84 @@ +package com.hanaro.hanafun.common.authentication; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.Base64; +import java.util.Collections; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtUtil { + + @Value("${jwt.secretKey}") + private String secretKey; + + @Value("${jwt.access-expired-ms}") + private Long accessExpiredMS; + + @Value("${jwt.claims.auth-key}") + private String authKey; + + @PostConstruct + protected void init() { + secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); + } + + public String createToken(String username, Long userId) { + Claims claims = Jwts.claims().setSubject(username); + claims.put(authKey, userId); + + long now = System.currentTimeMillis(); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date(now)) + .setExpiration(new Date(now + accessExpiredMS)) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } + + //Jwt 파싱하여 만료여부를 확인한다. + public Boolean isExpired(String token) { + try { + return getClaims(token).getExpiration().before(new Date()); + } catch (Exception e) { + e.printStackTrace(); + return true; // 만료된 것으로 간주 + } + } + + //Jwt 파싱하여 인증 정보를 반환한다. + public long getAuthValue(String token) { + try { + return getClaims(token).get(authKey, Long.class); + } catch (Exception e) { + e.printStackTrace(); + return 0; // 기본값 반환 + } + } + + //클래임 건지기 + public Claims getClaims(String token){ + return Jwts.parser() + .setSigningKey(secretKey) + .parseClaimsJws(token) + .getBody(); + } + + //Jwt 인증 정보를 토큰으로 반환 + public Authentication getAuthFromJwt(String token) { + Long userId = getAuthValue(token); + return new UsernamePasswordAuthenticationToken(userId, null, + Collections.singleton(new SimpleGrantedAuthority("USER"))); + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/config/S3Config.java b/src/main/java/com/hanaro/hanafun/common/config/S3Config.java new file mode 100644 index 0000000..cef8cba --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/config/S3Config.java @@ -0,0 +1,32 @@ +package com.hanaro.hanafun.common.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + + return (AmazonS3Client) AmazonS3ClientBuilder + .standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .build(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/config/SecurityConfig.java b/src/main/java/com/hanaro/hanafun/common/config/SecurityConfig.java new file mode 100644 index 0000000..f0f7bfe --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/config/SecurityConfig.java @@ -0,0 +1,66 @@ +package com.hanaro.hanafun.common.config; + +import com.hanaro.hanafun.common.authentication.JwtAuthFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig { + private final JwtAuthFilter jwtAuthFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception, AuthenticationException { + http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + .httpBasic(httpBasic -> httpBasic.disable()) + .formLogin(formLogin -> formLogin.disable()) + .sessionManagement((sessionManagement) -> + sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authorizeHttpRequests((auth) -> auth + .requestMatchers("/user/login").permitAll() +// .anyRequest().permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(corsFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public UrlBasedCorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // TODO CORS 설정 변경 필요 + config.addAllowedOrigin("http://localhost:3000"); + config.addAllowedOrigin("https://hana-fun-fe.vercel.app"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } + + @Bean + public CorsFilter corsFilter() { + return new CorsFilter(corsConfigurationSource()); + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/domain/BaseEntity.java b/src/main/java/com/hanaro/hanafun/common/domain/BaseEntity.java new file mode 100644 index 0000000..20067d9 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/domain/BaseEntity.java @@ -0,0 +1,34 @@ +package com.hanaro.hanafun.common.domain; + +import jakarta.persistence.*; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + @Column(updatable = false) + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime updatedDate; + + @PrePersist + protected void onCreate() { + if (createdDate == null) { + createdDate = LocalDateTime.now(); + } + updatedDate = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedDate = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/common/dto/ApiResponse.java b/src/main/java/com/hanaro/hanafun/common/dto/ApiResponse.java index 8f9591f..8e788f7 100644 --- a/src/main/java/com/hanaro/hanafun/common/dto/ApiResponse.java +++ b/src/main/java/com/hanaro/hanafun/common/dto/ApiResponse.java @@ -1,5 +1,6 @@ package com.hanaro.hanafun.common.dto; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.AllArgsConstructor; @@ -7,14 +8,15 @@ @Getter @AllArgsConstructor -@JsonPropertyOrder({"isSuccess", "code", "message", "data"}) +@JsonPropertyOrder({"isSuccess", "message", "data"}) public class ApiResponse { @JsonProperty("isSuccess") private final Boolean isSuccess; - @JsonProperty("code") - private final String code; + @JsonProperty("message") private final String message; + + @JsonInclude(JsonInclude.Include.NON_NULL) @JsonProperty("data") private T data; } \ No newline at end of file diff --git a/src/main/java/com/hanaro/hanafun/common/exception/BasicErrorStatus.java b/src/main/java/com/hanaro/hanafun/common/exception/BasicErrorStatus.java index 4c176af..b03bcc8 100644 --- a/src/main/java/com/hanaro/hanafun/common/exception/BasicErrorStatus.java +++ b/src/main/java/com/hanaro/hanafun/common/exception/BasicErrorStatus.java @@ -7,13 +7,12 @@ @Getter @AllArgsConstructor public enum BasicErrorStatus { - BAD_REQUEST(HttpStatus.BAD_REQUEST, "E001", "잘못된 요청입니다."), - UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "E002", "인증이 필요합니다."), - RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "E003", "값을 찾을 수 없습니다."), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "E004", "서버 에러"), - FORBIDDEN(HttpStatus.FORBIDDEN, "E005", "금지된 작업입니다."); + BAD_REQUEST(HttpStatus.BAD_REQUEST, "BAD_REQUEST"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "NO_ACCESS_AUTH"), + RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "NOT_FOUND"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR"), + FORBIDDEN(HttpStatus.FORBIDDEN, "FORBIDDEN"); private final HttpStatus httpStatus; - private final String code; private final String message; -} +} \ No newline at end of file diff --git a/src/main/java/com/hanaro/hanafun/common/exception/CustomException.java b/src/main/java/com/hanaro/hanafun/common/exception/CustomException.java new file mode 100644 index 0000000..45ed1c3 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/exception/CustomException.java @@ -0,0 +1,12 @@ +package com.hanaro.hanafun.common.exception; + +import org.springframework.http.HttpStatus; + +public abstract class CustomException extends RuntimeException{ + + public CustomException(String message) { + super(message); + } + + public abstract HttpStatus getHttpStatus(); +} diff --git a/src/main/java/com/hanaro/hanafun/common/exception/GlobalExceptionHandler.java b/src/main/java/com/hanaro/hanafun/common/exception/GlobalExceptionHandler.java deleted file mode 100644 index c9bb40f..0000000 --- a/src/main/java/com/hanaro/hanafun/common/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.hanaro.hanafun.common.exception; - -import com.hanaro.hanafun.common.dto.ApiResponse; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@RestControllerAdvice -public class GlobalExceptionHandler { - @ExceptionHandler(Exception.class) - public ResponseEntity> handleGlobalException(Exception ex) { - BasicErrorStatus error = BasicErrorStatus.INTERNAL_SERVER_ERROR; - ApiResponse response = new ApiResponse<>(false, error.getCode(), error.getMessage(), null); - return new ResponseEntity<>(response, error.getHttpStatus()); - } -} diff --git a/src/main/java/com/hanaro/hanafun/common/scheduler/AutoTransferScheduler.java b/src/main/java/com/hanaro/hanafun/common/scheduler/AutoTransferScheduler.java new file mode 100644 index 0000000..9d178b3 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/common/scheduler/AutoTransferScheduler.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.common.scheduler; + +import com.hanaro.hanafun.transaction.service.impl.TransactionServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AutoTransferScheduler { + private final TransactionServiceImpl transactionService; + + @Scheduled(cron = "0 0 0 * * ?") //매일 0시 + public void runAutoTransfer(){ + transactionService.autoTransfer(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/hanastorage/domain/HanastorageEntity.java b/src/main/java/com/hanaro/hanafun/hanastorage/domain/HanastorageEntity.java new file mode 100644 index 0000000..3ab4481 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/hanastorage/domain/HanastorageEntity.java @@ -0,0 +1,30 @@ +package com.hanaro.hanafun.hanastorage.domain; + +import com.hanaro.hanafun.transaction.domain.TransactionEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; + +@Entity +@Table(name = "hanastorage") +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class HanastorageEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "hanastorage_id") + private Long hanastorageId; + + @OneToOne + @JoinColumn(name = "transaction_id") + private TransactionEntity transactionEntity; + + @Column(name = "lessondate") + private LocalDate lessondate; + + @Column(name = "is_deleted") + private Boolean isDeleted; +} diff --git a/src/main/java/com/hanaro/hanafun/hanastorage/domain/HanastorageRepository.java b/src/main/java/com/hanaro/hanafun/hanastorage/domain/HanastorageRepository.java new file mode 100644 index 0000000..0d7bfb1 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/hanastorage/domain/HanastorageRepository.java @@ -0,0 +1,13 @@ +package com.hanaro.hanafun.hanastorage.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +public interface HanastorageRepository extends JpaRepository { + + List findByLessondateAndIsDeleted(LocalDate lessondate, Boolean isDeleted); + Optional findByTransactionEntityTransactionId(Long transactionId); +} diff --git a/src/main/java/com/hanaro/hanafun/host/controller/HostController.java b/src/main/java/com/hanaro/hanafun/host/controller/HostController.java new file mode 100644 index 0000000..048435a --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/controller/HostController.java @@ -0,0 +1,31 @@ +package com.hanaro.hanafun.host.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.host.dto.CreateHostReqDto; +import com.hanaro.hanafun.host.dto.CreateHostResDto; +import com.hanaro.hanafun.host.dto.HostInfoResDto; +import com.hanaro.hanafun.host.service.impl.HostServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/host") +public class HostController { + + private final HostServiceImpl hostService; + + @PostMapping("/create") + public ResponseEntity createHost(@AuthenticationPrincipal Long userId, @RequestBody CreateHostReqDto createHostReqDto){ + CreateHostResDto createHostResDto = hostService.createHost(userId, createHostReqDto); + return ResponseEntity.ok(new ApiResponse(true, "ok", createHostResDto)); + } + + @GetMapping("/info") + public ResponseEntity readHostInfo(@AuthenticationPrincipal Long userId){ + HostInfoResDto hostInfoResDto = hostService.readHostInfo(userId); + return ResponseEntity.ok(new ApiResponse(true, "ok", hostInfoResDto)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/host/domain/HostEntity.java b/src/main/java/com/hanaro/hanafun/host/domain/HostEntity.java new file mode 100644 index 0000000..f54162f --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/domain/HostEntity.java @@ -0,0 +1,31 @@ +package com.hanaro.hanafun.host.domain; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "host") +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class HostEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "host_id") + private Long hostId; + + @OneToOne(optional = false) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity userEntity; + + @Column(name = "introduction", nullable = false) + private String introduction; + + @OneToOne(optional = false) + @JoinColumn(name = "account_id", nullable = false) + private AccountEntity accountEntity; +} diff --git a/src/main/java/com/hanaro/hanafun/host/domain/HostRepository.java b/src/main/java/com/hanaro/hanafun/host/domain/HostRepository.java new file mode 100644 index 0000000..9705895 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/domain/HostRepository.java @@ -0,0 +1,10 @@ +package com.hanaro.hanafun.host.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface HostRepository extends JpaRepository { + Optional findByUserEntityUserId(Long userId); + HostEntity findHostEntityByUserEntity_UserId(Long userId); +} diff --git a/src/main/java/com/hanaro/hanafun/host/dto/CreateHostReqDto.java b/src/main/java/com/hanaro/hanafun/host/dto/CreateHostReqDto.java new file mode 100644 index 0000000..9d91f4c --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/dto/CreateHostReqDto.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.host.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CreateHostReqDto { + private long accountId; + private String introduction; +} diff --git a/src/main/java/com/hanaro/hanafun/host/dto/CreateHostResDto.java b/src/main/java/com/hanaro/hanafun/host/dto/CreateHostResDto.java new file mode 100644 index 0000000..d3b5582 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/dto/CreateHostResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.host.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CreateHostResDto { + private Long hostId; +} diff --git a/src/main/java/com/hanaro/hanafun/host/dto/HostAccountDto.java b/src/main/java/com/hanaro/hanafun/host/dto/HostAccountDto.java new file mode 100644 index 0000000..bb160d4 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/dto/HostAccountDto.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.host.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class HostAccountDto { + private long accountId; + private String accountName; + private String accountNumber; + private int balance; +} diff --git a/src/main/java/com/hanaro/hanafun/host/dto/HostInfoResDto.java b/src/main/java/com/hanaro/hanafun/host/dto/HostInfoResDto.java new file mode 100644 index 0000000..e730562 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/dto/HostInfoResDto.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.host.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class HostInfoResDto { + private HostAccountDto account; + private List lessonList; +} diff --git a/src/main/java/com/hanaro/hanafun/host/dto/HostLessonDto.java b/src/main/java/com/hanaro/hanafun/host/dto/HostLessonDto.java new file mode 100644 index 0000000..c19a174 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/dto/HostLessonDto.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.host.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class HostLessonDto { + private Long lessonId; + private String title; +} diff --git a/src/main/java/com/hanaro/hanafun/host/exception/HostNotFoundException.java b/src/main/java/com/hanaro/hanafun/host/exception/HostNotFoundException.java new file mode 100644 index 0000000..d8b76cc --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/exception/HostNotFoundException.java @@ -0,0 +1,16 @@ +package com.hanaro.hanafun.host.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class HostNotFoundException extends CustomException { + static String MESSAGE = "HOST_NOT_FOUND"; + + public HostNotFoundException(){ + super(MESSAGE); + } + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/host/mapper/HostMapper.java b/src/main/java/com/hanaro/hanafun/host/mapper/HostMapper.java new file mode 100644 index 0000000..9310cc8 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/mapper/HostMapper.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.host.mapper; + +import com.hanaro.hanafun.host.dto.HostLessonDto; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class HostMapper { + public static HostLessonDto LessonEntityToHostLessonDto(LessonEntity lessonEntity){ + if(lessonEntity == null) return null; + + return new HostLessonDto().builder() + .lessonId(lessonEntity.getLessonId()) + .title(lessonEntity.getTitle()) + .build(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/host/service/HostService.java b/src/main/java/com/hanaro/hanafun/host/service/HostService.java new file mode 100644 index 0000000..cdc99ab --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/service/HostService.java @@ -0,0 +1,11 @@ +package com.hanaro.hanafun.host.service; + +import com.hanaro.hanafun.host.dto.CreateHostReqDto; +import com.hanaro.hanafun.host.dto.CreateHostResDto; +import com.hanaro.hanafun.host.dto.HostInfoResDto; + +public interface HostService { + CreateHostResDto createHost(Long userId, CreateHostReqDto createHostReqDto); + + HostInfoResDto readHostInfo(Long userId); +} diff --git a/src/main/java/com/hanaro/hanafun/host/service/impl/HostServiceImpl.java b/src/main/java/com/hanaro/hanafun/host/service/impl/HostServiceImpl.java new file mode 100644 index 0000000..6feefcd --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/host/service/impl/HostServiceImpl.java @@ -0,0 +1,70 @@ +package com.hanaro.hanafun.host.service.impl; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.account.domain.AccountRepository; +import com.hanaro.hanafun.account.exception.AccountNotFoundException; +import com.hanaro.hanafun.host.domain.HostEntity; +import com.hanaro.hanafun.host.domain.HostRepository; +import com.hanaro.hanafun.host.dto.*; +import com.hanaro.hanafun.host.exception.HostNotFoundException; +import com.hanaro.hanafun.host.mapper.HostMapper; +import com.hanaro.hanafun.host.service.HostService; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import com.hanaro.hanafun.lesson.domain.LessonRepository; +import com.hanaro.hanafun.user.domain.UserEntity; +import com.hanaro.hanafun.user.domain.UserRepository; +import com.hanaro.hanafun.user.exception.UserNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class HostServiceImpl implements HostService { + private final UserRepository userRepository; + private final AccountRepository accountRepository; + private final LessonRepository lessonRepository; + private final HostRepository hostRepository; + + @Override + @Transactional + public CreateHostResDto createHost(Long userId, CreateHostReqDto createHostReqDto) { + UserEntity userEntity = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException()); + userEntity.setIsHost(true); + + AccountEntity accountEntity = accountRepository.findById(createHostReqDto.getAccountId()).orElseThrow(() -> new AccountNotFoundException()); + + HostEntity hostEntity = HostEntity.builder() + .userEntity(userEntity) + .introduction(createHostReqDto.getIntroduction()) + .accountEntity(accountEntity) + .build(); + HostEntity createdHost = hostRepository.save(hostEntity); + + return new CreateHostResDto().builder() + .hostId(createdHost.getHostId()) + .build(); + } + + @Override + public HostInfoResDto readHostInfo(Long userId) { + HostEntity hostEntity = hostRepository.findByUserEntityUserId(userId).orElseThrow(() -> new HostNotFoundException()); + AccountEntity accountEntity = hostEntity.getAccountEntity(); + HostAccountDto hostAccountDto = HostAccountDto.builder() + .accountId(accountEntity.getAccountId()) + .accountName(accountEntity.getAccountName()) + .accountNumber(accountEntity.getAccountNumber()) + .balance(accountEntity.getBalance()) + .build(); + + List lessonEntityList = lessonRepository.findByHostEntityHostId(hostEntity.getHostId()).orElseThrow(); + List hostLessonDtoList = lessonEntityList.stream().map(HostMapper::LessonEntityToHostLessonDto).toList(); + + return new HostInfoResDto().builder() + .account(hostAccountDto) + .lessonList(hostLessonDtoList) + .build(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/controller/LessonController.java b/src/main/java/com/hanaro/hanafun/lesson/controller/LessonController.java new file mode 100644 index 0000000..a707dcb --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/controller/LessonController.java @@ -0,0 +1,108 @@ +package com.hanaro.hanafun.lesson.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.lesson.dto.request.CreateLessonReqDto; +import com.hanaro.hanafun.lesson.dto.request.OpenedLessonsReqDto; +import com.hanaro.hanafun.lesson.dto.response.FullLessonResDto; +import com.hanaro.hanafun.lesson.dto.response.LessonInfoResDto; +import com.hanaro.hanafun.lesson.dto.response.OpenedLessonsResDto; +import com.hanaro.hanafun.lesson.service.LessonService; +import com.hanaro.hanafun.lesson.service.S3UploadService; +import com.hanaro.hanafun.lessondate.dto.response.LessonDetailResDto; +import com.hanaro.hanafun.lessondate.service.LessonDateService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class LessonController { + private final LessonService lessonService; + private final LessonDateService lessonDateService; + private final S3UploadService s3UploadService; + + // 개설 클래스 관리- 개설 클래스 목록 출력 + @GetMapping("/reservation/my/opened") + public ResponseEntity openedLessons(@AuthenticationPrincipal Long userId) { + List openedLessons = lessonService.openedLessons(userId); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", openedLessons)); + } + + // 개설 클래스 상세 + @GetMapping("/reservation/my/opened/{lessonId}") + public ResponseEntity lessonDetail(@PathVariable Long lessonId) { + List lessonDetails = lessonDateService.lessonDetail(lessonId); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", lessonDetails)); + } + + // 클래스 상세보기 + @GetMapping("/lesson/{lessonId}") + public ResponseEntity lessonInfo(@PathVariable Long lessonId) { + LessonInfoResDto lessonInfoResDto = lessonService.lessonInfo(lessonId); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", lessonInfoResDto)); + } + + // 클래스 이미지 업로드 + @PostMapping("/lesson/image-upload") + public ResponseEntity uploadImage(@RequestParam("file") MultipartFile file) { + try { + String imageUrl = s3UploadService.saveFile(file); + return ResponseEntity.ok(imageUrl); + } catch (IOException e) { + return ResponseEntity.status(500).body("파일 업로드 중 오류가 발생했습니다."); + } + } + + // 클래스 등록하기 + @PostMapping("/lesson/create") + public void createLesson(@RequestBody CreateLessonReqDto createLessonReqDto) { + lessonService.createLesson(createLessonReqDto); + } + + // 클래스 전체 조회 (클래스 탐색) + @GetMapping("/category/search/all") + public ResponseEntity fullLesson() { + List fullLessonResDtos = lessonService.fullLesson(); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", fullLessonResDtos)); + } + + // 카테고리 별 클래스 조회 + @GetMapping("/category/search/{categoryId}") + public ResponseEntity categoryLesson(@PathVariable Long categoryId) { + List fullLessonResDtos = lessonService.categoryLesson(categoryId); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", fullLessonResDtos)); + } + + // 클래스 검색(전체) + @GetMapping("/category/all/search") + public ResponseEntity searchLessonAll(@RequestParam("query") String query){ + List fullLessonResDtos = lessonService.searchLessonAll(query); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", fullLessonResDtos)); + } + + // 클래스 검색(카테고리) + @GetMapping("/category/{categoryId}/search") + public ResponseEntity searchLessonCategory(@PathVariable Long categoryId, @RequestParam("query") String query){ + List fullLessonResDtos = lessonService.searchLessonCategory(categoryId, query); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", fullLessonResDtos)); + } + + // 클래스 필터(전체) + @GetMapping("/category/all") + public ResponseEntity searchFilterLessonAll(@RequestParam("query") String query, @RequestParam("sort") String sort){ + List fullLessonResDtos = lessonService.searchFilterLessonAll(query, sort); + return ResponseEntity.ok(new ApiResponse(true, "ok", fullLessonResDtos)); + } + + // 클래스 필터(카테고리) + @GetMapping("/category/{categoryId}") + public ResponseEntity searchFilterLessonCategory(@PathVariable Long categoryId, @RequestParam("query") String query, @RequestParam("sort") String sort){ + List fullLessonResDtos = lessonService.searchFilterLessonCategory(categoryId, query, sort); + return ResponseEntity.ok(new ApiResponse(true, "ok", fullLessonResDtos)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/domain/LessonEntity.java b/src/main/java/com/hanaro/hanafun/lesson/domain/LessonEntity.java new file mode 100644 index 0000000..20048ee --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/domain/LessonEntity.java @@ -0,0 +1,63 @@ +package com.hanaro.hanafun.lesson.domain; + +import com.hanaro.hanafun.category.domain.CategoryEntity; +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.host.domain.HostEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +@Entity +@Table(name = "lesson") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor +@DynamicInsert +public class LessonEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long lessonId; + + @ManyToOne + @JoinColumn(name = "host_id", nullable = false) + private HostEntity hostEntity; + + @OneToOne + @JoinColumn(name = "category_id", nullable = false) + private CategoryEntity categoryEntity; + + @Column(nullable = false, length = 30) + private String title; + + @Column(nullable = false) + private String location; + + @Column(nullable = false) + private int price; + + @Column(nullable = false) + private int capacity; + + @Column(nullable = false) + private String image; + + @Column(nullable = false) + private String description; + + private String materials; + + @ColumnDefault("0") + @Column(nullable = false) + private int applicantSum; + + @ColumnDefault("0") + @Column(nullable = false) + private boolean isDeleted; + + // 강좌신청 누적인원 업데이트 + public void updateApplicantSum(int applicantSum) { + this.applicantSum = applicantSum; + } +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/domain/LessonRepository.java b/src/main/java/com/hanaro/hanafun/lesson/domain/LessonRepository.java new file mode 100644 index 0000000..026e9c2 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/domain/LessonRepository.java @@ -0,0 +1,40 @@ +package com.hanaro.hanafun.lesson.domain; + +import com.hanaro.hanafun.host.domain.HostEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface LessonRepository extends JpaRepository { + List findLessonEntitiesByHostEntity(HostEntity hostEntity); + Optional> findByHostEntityHostId(Long hostId); + Optional> findByCategoryEntityCategoryId(Long categoryId); + + @Query("SELECT L FROM LessonEntity L WHERE L.title Like %:query% OR L.description Like %:query%") + List findBySearchLessonAll(String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.categoryEntity.categoryId = :categoryId AND (L.title Like %:query% OR L.description Like %:query%)") + List findBySearchLessonCategory(Long categoryId, String query); + + @Query(value = "SELECT L FROM LessonEntity L WHERE L.title Like %:query% OR L.description Like %:query% ORDER BY L.createdDate DESC") + List findSearchFilterLessonAllByOrderByDate(String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.title Like %:query% OR L.description Like %:query% ORDER BY L.applicantSum DESC") + List findSearchFilterLessonAllByOrderByApplicantSum(String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.title Like %:query% OR L.description Like %:query% ORDER BY L.price ASC") + List findSearchFilterLessonAllByOrderByPriceAsc(String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.title Like %:query% OR L.description Like %:query% ORDER BY L.price DESC") + List findSearchFilterLessonAllByOrderByPriceDesc(String query); + + @Query(value = "SELECT L FROM LessonEntity L WHERE L.categoryEntity.categoryId = :categoryId AND (L.title Like %:query% OR L.description Like %:query%) ORDER BY L.createdDate DESC") + List findSearchFilterLessonCategoryByOrderByDate(Long categoryId, String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.categoryEntity.categoryId = :categoryId AND (L.title Like %:query% OR L.description Like %:query%) ORDER BY L.applicantSum DESC") + List findSearchFilterLessonCategoryByOrderByApplicantSum(Long categoryId, String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.categoryEntity.categoryId = :categoryId AND (L.title Like %:query% OR L.description Like %:query%) ORDER BY L.price ASC") + List findSearchFilterLessonCategoryByOrderByPriceAsc(Long categoryId, String query); + @Query(value = "SELECT L FROM LessonEntity L WHERE L.categoryEntity.categoryId = :categoryId AND (L.title Like %:query% OR L.description Like %:query%) ORDER BY L.price DESC") + List findSearchFilterLessonCategoryByOrderByPriceDesc(Long categoryId, String query); +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/dto/request/CreateLessonDateReqDto.java b/src/main/java/com/hanaro/hanafun/lesson/dto/request/CreateLessonDateReqDto.java new file mode 100644 index 0000000..882da02 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/dto/request/CreateLessonDateReqDto.java @@ -0,0 +1,19 @@ +package com.hanaro.hanafun.lesson.dto.request; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +public class CreateLessonDateReqDto { + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate date; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/dto/request/CreateLessonReqDto.java b/src/main/java/com/hanaro/hanafun/lesson/dto/request/CreateLessonReqDto.java new file mode 100644 index 0000000..207c653 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/dto/request/CreateLessonReqDto.java @@ -0,0 +1,19 @@ +package com.hanaro.hanafun.lesson.dto.request; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class CreateLessonReqDto { + private Long userId; + private Long categoryId; + private String title; + private String location; + private int price; + private int capacity; + private String image; + private String description; + private String materials; + private List lessonDate; +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/dto/request/OpenedLessonsReqDto.java b/src/main/java/com/hanaro/hanafun/lesson/dto/request/OpenedLessonsReqDto.java new file mode 100644 index 0000000..d1a6f1e --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/dto/request/OpenedLessonsReqDto.java @@ -0,0 +1,8 @@ +package com.hanaro.hanafun.lesson.dto.request; + +import lombok.Getter; + +@Getter +public class OpenedLessonsReqDto { + private Long userId; +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/dto/response/FullLessonResDto.java b/src/main/java/com/hanaro/hanafun/lesson/dto/response/FullLessonResDto.java new file mode 100644 index 0000000..92f8264 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/dto/response/FullLessonResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.lesson.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FullLessonResDto { + private Long lessonId; + private String image; + private String title; + private int price; + private String hostName; +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/dto/response/LessonInfoResDto.java b/src/main/java/com/hanaro/hanafun/lesson/dto/response/LessonInfoResDto.java new file mode 100644 index 0000000..2e91ea9 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/dto/response/LessonInfoResDto.java @@ -0,0 +1,18 @@ +package com.hanaro.hanafun.lesson.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class LessonInfoResDto { + private Long lessonId; + private String image; + private String title; + private int price; + private String description; + private String location; + private String materials; + private int capacity; + private String categoryName; +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/dto/response/OpenedLessonsResDto.java b/src/main/java/com/hanaro/hanafun/lesson/dto/response/OpenedLessonsResDto.java new file mode 100644 index 0000000..7ef18cb --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/dto/response/OpenedLessonsResDto.java @@ -0,0 +1,12 @@ +package com.hanaro.hanafun.lesson.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class OpenedLessonsResDto { + private Long lessonId; + private String image; + private String title; +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/exception/LessonNotFoundException.java b/src/main/java/com/hanaro/hanafun/lesson/exception/LessonNotFoundException.java new file mode 100644 index 0000000..ec3f7a3 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/exception/LessonNotFoundException.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.lesson.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class LessonNotFoundException extends CustomException { + static String MESSAGE = "LESSON_NOT_FOUND"; + + public LessonNotFoundException() { + super(MESSAGE); + } + + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/service/LessonService.java b/src/main/java/com/hanaro/hanafun/lesson/service/LessonService.java new file mode 100644 index 0000000..38d58ba --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/service/LessonService.java @@ -0,0 +1,38 @@ +package com.hanaro.hanafun.lesson.service; + +import com.hanaro.hanafun.lesson.dto.request.CreateLessonReqDto; +import com.hanaro.hanafun.lesson.dto.request.OpenedLessonsReqDto; +import com.hanaro.hanafun.lesson.dto.response.FullLessonResDto; +import com.hanaro.hanafun.lesson.dto.response.LessonInfoResDto; +import com.hanaro.hanafun.lesson.dto.response.OpenedLessonsResDto; + +import java.util.List; + +public interface LessonService { + // 개설 클래스 관리- 개설 클래스 목록 출력 + List openedLessons(Long userId); + + // 클래스 상세보기 + LessonInfoResDto lessonInfo(Long lessonId); + + // 클래스 등록하기 + void createLesson(CreateLessonReqDto createLessonReqDto); + + // 클래스 전체 조회 (클래스 탐색) + List fullLesson(); + + // 카테고리별 클래스 조회 + List categoryLesson(Long categoryId); + + // 클래스 검색(전체) + List searchLessonAll(String query); + + // 클래스 검색(카테고리) + List searchLessonCategory(Long categoryId, String query); + + // 클래스 필터(전체) + List searchFilterLessonAll(String query, String sort); + + // 클래스 필터(카테고리) + List searchFilterLessonCategory(Long categoryId, String query, String sort); +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/service/S3UploadService.java b/src/main/java/com/hanaro/hanafun/lesson/service/S3UploadService.java new file mode 100644 index 0000000..469a9ac --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/service/S3UploadService.java @@ -0,0 +1,34 @@ +package com.hanaro.hanafun.lesson.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@RequiredArgsConstructor +@Service +public class S3UploadService { + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String saveFile(MultipartFile multipartFile) throws IOException { + String originalFilename = multipartFile.getOriginalFilename(); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(multipartFile.getSize()); + metadata.setContentType(multipartFile.getContentType()); + + amazonS3.putObject(bucket, originalFilename, multipartFile.getInputStream(), metadata); + // 파일을 퍼블릭으로 설정 + amazonS3.setObjectAcl(bucket, originalFilename, CannedAccessControlList.PublicRead); + + return amazonS3.getUrl(bucket, originalFilename).toString(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/lesson/service/impl/LessonServiceImpl.java b/src/main/java/com/hanaro/hanafun/lesson/service/impl/LessonServiceImpl.java new file mode 100644 index 0000000..300e735 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lesson/service/impl/LessonServiceImpl.java @@ -0,0 +1,192 @@ +package com.hanaro.hanafun.lesson.service.impl; + +import com.hanaro.hanafun.category.domain.CategoryEntity; +import com.hanaro.hanafun.category.domain.CategoryRepository; +import com.hanaro.hanafun.category.exception.CategoryNotFoundException; +import com.hanaro.hanafun.host.domain.HostEntity; +import com.hanaro.hanafun.host.domain.HostRepository; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import com.hanaro.hanafun.lesson.domain.LessonRepository; +import com.hanaro.hanafun.lesson.dto.request.CreateLessonDateReqDto; +import com.hanaro.hanafun.lesson.dto.request.CreateLessonReqDto; +import com.hanaro.hanafun.lesson.dto.request.OpenedLessonsReqDto; +import com.hanaro.hanafun.lesson.dto.response.FullLessonResDto; +import com.hanaro.hanafun.lesson.dto.response.LessonInfoResDto; +import com.hanaro.hanafun.lesson.dto.response.OpenedLessonsResDto; +import com.hanaro.hanafun.lesson.exception.LessonNotFoundException; +import com.hanaro.hanafun.lesson.service.LessonService; +import com.hanaro.hanafun.lessondate.domain.LessonDateEntity; +import com.hanaro.hanafun.lessondate.domain.LessonDateRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class LessonServiceImpl implements LessonService { + private final LessonRepository lessonRepository; + private final HostRepository hostRepository; + private final CategoryRepository categoryRepository; + private final LessonDateRepository lessonDateRepository; + + // 개설 클래스 관리- 개설 클래스 목록 출력 + @Transactional + @Override + public List openedLessons(Long userId) { + HostEntity host = hostRepository.findHostEntityByUserEntity_UserId(userId); + List lessons = lessonRepository.findLessonEntitiesByHostEntity(host); + + List openedLessons = lessons.stream() + .map(lesson -> { + OpenedLessonsResDto openedLesson = OpenedLessonsResDto.builder() + .lessonId(lesson.getLessonId()) + .image(lesson.getImage()) + .title(lesson.getTitle()) + .build(); + return openedLesson; + }) + .collect(Collectors.toList()); + + return openedLessons; + } + + // 클래스 상세보기 + @Transactional + @Override + public LessonInfoResDto lessonInfo(Long lessonId) { + LessonEntity lesson = lessonRepository.findById(lessonId).orElseThrow(() -> new LessonNotFoundException()); + + LessonInfoResDto lessonInfoResDto = LessonInfoResDto.builder() + .lessonId(lessonId) + .image(lesson.getImage()) + .title(lesson.getTitle()) + .price(lesson.getPrice()) + .description(lesson.getDescription()) + .location(lesson.getLocation()) + .materials(lesson.getMaterials()) + .capacity(lesson.getCapacity()) + .categoryName(lesson.getCategoryEntity().getCategoryName()) + .build(); + + return lessonInfoResDto; + } + + // 클래스 등록하기 + @Transactional + @Override + public void createLesson(CreateLessonReqDto createLessonReqDto) { + HostEntity host = hostRepository.findHostEntityByUserEntity_UserId(createLessonReqDto.getUserId()); + CategoryEntity category = categoryRepository.findById(createLessonReqDto.getCategoryId()).orElseThrow(() -> new CategoryNotFoundException()); + + // Lesson 추가 + LessonEntity lesson = LessonEntity.builder() + .hostEntity(host) + .categoryEntity(category) + .title(createLessonReqDto.getTitle()) + .location(createLessonReqDto.getLocation()) + .price(createLessonReqDto.getPrice()) + .capacity(createLessonReqDto.getCapacity()) + .image(createLessonReqDto.getImage()) + .description(createLessonReqDto.getDescription()) + .materials(createLessonReqDto.getMaterials()) + .build(); + lessonRepository.save(lesson); + + // LessonDate 추가 + List lessonDates = createLessonReqDto.getLessonDate(); + for (CreateLessonDateReqDto lessonDateReqDto: lessonDates) { + LessonDateEntity lessonDate = LessonDateEntity.builder() + .lessonEntity(lesson) + .date(lessonDateReqDto.getDate()) + .startTime(lessonDateReqDto.getStartTime()) + .endTime(lessonDateReqDto.getEndTime()) + .build(); + lessonDateRepository.save(lessonDate); + } + } + + // 클래스 전체 조회 (클래스 탐색) + @Transactional + @Override + public List fullLesson() { + List lessonList = lessonRepository.findAll(); + + return searchLessonList(lessonList); + } + + // 카테고리 별 클래스 조회 + @Transactional + @Override + public List categoryLesson(Long categoryId) { + List lessonList = lessonRepository.findByCategoryEntityCategoryId(categoryId) + .orElseThrow(()->new CategoryNotFoundException()); + + if(lessonList.isEmpty()) throw new CategoryNotFoundException(); + + return searchLessonList(lessonList); + } + + // 클래스 검색(전체) + @Transactional + @Override + public List searchLessonAll(String query) { + List lessonList = lessonRepository.findBySearchLessonAll(query); + + return searchLessonList(lessonList); + } + + // 클래스 검색(카테고리) + @Transactional + @Override + public List searchLessonCategory(Long categoryId, String query) { + List lessonList = lessonRepository.findBySearchLessonCategory(categoryId, query); + + return searchLessonList(lessonList); + } + + // 클래스 필터(전체) + @Transactional + @Override + public List searchFilterLessonAll(String query, String sort) { + List lessonList = switch (sort) { + case "date" -> lessonRepository.findSearchFilterLessonAllByOrderByDate(query); + case "popular" -> lessonRepository.findSearchFilterLessonAllByOrderByApplicantSum(query); + case "priceAsc" -> lessonRepository.findSearchFilterLessonAllByOrderByPriceAsc(query); + case "priceDesc" -> lessonRepository.findSearchFilterLessonAllByOrderByPriceDesc(query); + default -> throw new LessonNotFoundException(); + }; + + return searchLessonList(lessonList); + } + + @Transactional + @Override + public List searchFilterLessonCategory(Long categoryId, String query, String sort) { + List lessonList = switch (sort) { + case "date" -> lessonRepository.findSearchFilterLessonCategoryByOrderByDate(categoryId, query); + case "popular" -> lessonRepository.findSearchFilterLessonCategoryByOrderByApplicantSum(categoryId, query); + case "priceAsc" -> lessonRepository.findSearchFilterLessonCategoryByOrderByPriceAsc(categoryId, query); + case "priceDesc" -> lessonRepository.findSearchFilterLessonCategoryByOrderByPriceDesc(categoryId, query); + default -> throw new LessonNotFoundException(); + }; + return searchLessonList(lessonList); + } + + public List searchLessonList(List lessonlist){ + return lessonlist.stream().map(lesson -> { + FullLessonResDto fullLessonResDto = FullLessonResDto.builder() + .lessonId(lesson.getLessonId()) + .image(lesson.getImage()) + .title(lesson.getTitle()) + .price(lesson.getPrice()) + .hostName(lesson.getHostEntity().getUserEntity().getUsername()) + .build(); + return fullLessonResDto; + }).collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/controller/LessonDateController.java b/src/main/java/com/hanaro/hanafun/lessondate/controller/LessonDateController.java new file mode 100644 index 0000000..2cfdfc1 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/controller/LessonDateController.java @@ -0,0 +1,26 @@ +package com.hanaro.hanafun.lessondate.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.lessondate.dto.request.AvailableDateReqDto; +import com.hanaro.hanafun.lessondate.dto.response.AvailableDateResDto; +import com.hanaro.hanafun.lessondate.service.LessonDateService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class LessonDateController { + private final LessonDateService lessonDateService; + + // 클래스 예약 가능 날짜 출력 + @GetMapping("/lesson/date-select") + public ResponseEntity availableDate(@RequestBody AvailableDateReqDto availableDateReqDto) { + List availableDateResDtoList = lessonDateService.availableDate(availableDateReqDto); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", availableDateResDtoList)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/domain/LessonDateEntity.java b/src/main/java/com/hanaro/hanafun/lessondate/domain/LessonDateEntity.java new file mode 100644 index 0000000..4f04c4f --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/domain/LessonDateEntity.java @@ -0,0 +1,46 @@ +package com.hanaro.hanafun.lessondate.domain; + +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Table(name = "lessondate") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor +@DynamicInsert +public class LessonDateEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long lessondateId; + + @ManyToOne + @JoinColumn(name = "lesson_id", nullable = false) + private LessonEntity lessonEntity; + + @Column(nullable = false) + private LocalDate date; + + @Column(nullable = false) + private LocalDateTime startTime; + + @Column(nullable = false) + private LocalDateTime endTime; + + @ColumnDefault("0") + @Column(nullable = false) + private int applicant; + + // 신청인원 업데이트 + public void updateApplicant(int applicant) { + this.applicant = applicant; + } +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/domain/LessonDateRepository.java b/src/main/java/com/hanaro/hanafun/lessondate/domain/LessonDateRepository.java new file mode 100644 index 0000000..b194344 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/domain/LessonDateRepository.java @@ -0,0 +1,13 @@ +package com.hanaro.hanafun.lessondate.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface LessonDateRepository extends JpaRepository { + List findLessonDateEntitiesByLessonEntity_LessonId(Long lessonId); + Optional findLessonDateEntityByLessondateId(Long lessondateId); +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/dto/request/AvailableDateReqDto.java b/src/main/java/com/hanaro/hanafun/lessondate/dto/request/AvailableDateReqDto.java new file mode 100644 index 0000000..be291bd --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/dto/request/AvailableDateReqDto.java @@ -0,0 +1,8 @@ +package com.hanaro.hanafun.lessondate.dto.request; + +import lombok.Getter; + +@Getter +public class AvailableDateReqDto { + private Long lessonId; +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/dto/response/AvailableDateResDto.java b/src/main/java/com/hanaro/hanafun/lessondate/dto/response/AvailableDateResDto.java new file mode 100644 index 0000000..c3f1d71 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/dto/response/AvailableDateResDto.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.lessondate.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Getter +@Builder +public class AvailableDateResDto { + private Long lessondateId; + private LocalDate date; + private LocalDateTime startTime; + private LocalDateTime endTime; + private int quantityLeft; // 잔여수량 (모집인원 - 신청인원) +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/dto/response/LessonDetailResDto.java b/src/main/java/com/hanaro/hanafun/lessondate/dto/response/LessonDetailResDto.java new file mode 100644 index 0000000..fb41fbb --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/dto/response/LessonDetailResDto.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.lessondate.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +@Builder +public class LessonDetailResDto { + private Long lessondateId; + private LocalDate date; + private Long lessonId; + private String title; +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/exception/LessonDateNotFoundException.java b/src/main/java/com/hanaro/hanafun/lessondate/exception/LessonDateNotFoundException.java new file mode 100644 index 0000000..e2c972b --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/exception/LessonDateNotFoundException.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.lessondate.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class LessonDateNotFoundException extends CustomException { + static String MESSAGE = "LESSONDATE_NOT_FOUND"; + + public LessonDateNotFoundException() { + super(MESSAGE); + } + + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/service/LessonDateService.java b/src/main/java/com/hanaro/hanafun/lessondate/service/LessonDateService.java new file mode 100644 index 0000000..d3fcbd3 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/service/LessonDateService.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.lessondate.service; + +import com.hanaro.hanafun.lessondate.dto.request.AvailableDateReqDto; +import com.hanaro.hanafun.lessondate.dto.response.AvailableDateResDto; +import com.hanaro.hanafun.lessondate.dto.response.LessonDetailResDto; + +import java.util.List; + +public interface LessonDateService { + // 개설 클래스 상세 + List lessonDetail(Long lessonId); + + // 클래스 예약 가능 날짜 출력 + List availableDate(AvailableDateReqDto availableDateReqDto); +} diff --git a/src/main/java/com/hanaro/hanafun/lessondate/service/impl/LessonDateServiceImpl.java b/src/main/java/com/hanaro/hanafun/lessondate/service/impl/LessonDateServiceImpl.java new file mode 100644 index 0000000..36dfb37 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/lessondate/service/impl/LessonDateServiceImpl.java @@ -0,0 +1,74 @@ +package com.hanaro.hanafun.lessondate.service.impl; + +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import com.hanaro.hanafun.lessondate.domain.LessonDateEntity; +import com.hanaro.hanafun.lessondate.domain.LessonDateRepository; +import com.hanaro.hanafun.lessondate.dto.request.AvailableDateReqDto; +import com.hanaro.hanafun.lessondate.dto.response.AvailableDateResDto; +import com.hanaro.hanafun.lessondate.dto.response.LessonDetailResDto; +import com.hanaro.hanafun.lessondate.service.LessonDateService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor + +public class LessonDateServiceImpl implements LessonDateService { + private final LessonDateRepository lessonDateRepository; + + // 개설 클래스 상세 + @Transactional + @Override + public List lessonDetail(Long lessonId) { + List lessonDates = lessonDateRepository.findLessonDateEntitiesByLessonEntity_LessonId(lessonId); + + List lessonDetails = lessonDates.stream() + .map(lessonDate -> { + LessonDetailResDto lessonDetail = LessonDetailResDto.builder() + .lessondateId(lessonDate.getLessondateId()) + .date(lessonDate.getDate()) + .lessonId(lessonId) + .title(lessonDate.getLessonEntity().getTitle()) + .build(); + return lessonDetail; + }) + .collect(Collectors.toList()); + + return lessonDetails; + } + + // 클래스 예약 가능 날짜 출력 + @Transactional + @Override + public List availableDate(AvailableDateReqDto availableDateReqDto) { + List lessonDates = lessonDateRepository.findLessonDateEntitiesByLessonEntity_LessonId(availableDateReqDto.getLessonId()); + + List availableDateResDtos = lessonDates.stream() + .map(lessonDate -> { + LessonEntity lesson = lessonDate.getLessonEntity(); + int quantityLeft = lesson.getCapacity() - lessonDate.getApplicant(); + + // 잔여 수량 0 이하 날짜 필터링 + if (quantityLeft <= 0) { + return null; + } + + return AvailableDateResDto.builder() + .lessondateId(lessonDate.getLessondateId()) + .date(lessonDate.getDate()) + .startTime(lessonDate.getStartTime()) + .endTime(lessonDate.getEndTime()) + .quantityLeft(quantityLeft) + .build(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return availableDateResDtos; + } +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/controller/ReservationController.java b/src/main/java/com/hanaro/hanafun/reservation/controller/ReservationController.java new file mode 100644 index 0000000..8fcf120 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/controller/ReservationController.java @@ -0,0 +1,59 @@ +package com.hanaro.hanafun.reservation.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.reservation.dto.request.*; +import com.hanaro.hanafun.reservation.dto.response.*; +import com.hanaro.hanafun.reservation.service.ReservationService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/reservation") +public class ReservationController { + private final ReservationService reservationService; + + // 마이페이지 데이터 출력 + @GetMapping("/my") + public ResponseEntity myPage(@RequestBody MyPageReqDto myPageReqDto) { + MyPageResDto myPageResDto = reservationService.myPage(myPageReqDto); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", myPageResDto)); + } + + // 나의 신청 클래스 데이터 출력 + @GetMapping("/my/lessons") + public ResponseEntity myLessons(@RequestBody MyPageReqDto myPageReqDto) { + List lessons = reservationService.myLessons(myPageReqDto); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", lessons)); + } + + // 신청 클래스 일정 데이터 출력 + @GetMapping("/my/schedule") + public ResponseEntity mySchedules(@RequestBody MyScheduleReqDto myScheduleReqDto) { + List mySchedules = reservationService.mySchedules(myScheduleReqDto); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", mySchedules)); + } + + // 개설 클래스 상세- 강좌날짜 별 예약자 정보 출력 + @PostMapping("/my/opened/people") + public ResponseEntity lessonDateDetail(@RequestBody LessonDateDetailReqDto lessonDateDetailReqDto) { + LessonDateDetailResDto lessonDateDetailResDto = reservationService.lessonDateDetail(lessonDateDetailReqDto); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", lessonDateDetailResDto)); + } + + // 클래스 예약하기 (결제 제외) + @PostMapping("/check") + public ResponseEntity bookLesson(@RequestBody BookLessonReqDto bookLessonReqDto) { + BookLessonResDto bookLessonResDto = reservationService.bookLesson(bookLessonReqDto); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", bookLessonResDto)); + } + + // 클래스 취소하기 (환불 제외) + @PostMapping("/cancel") + public void cancelLesson(@RequestBody CancelLessonReqDto cancelLessonReqDto) { + reservationService.cancelLesson(cancelLessonReqDto); + } +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/domain/ReservationEntity.java b/src/main/java/com/hanaro/hanafun/reservation/domain/ReservationEntity.java new file mode 100644 index 0000000..c294e27 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/domain/ReservationEntity.java @@ -0,0 +1,45 @@ +package com.hanaro.hanafun.reservation.domain; + +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.lessondate.domain.LessonDateEntity; +import com.hanaro.hanafun.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "reservation") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor +@DynamicInsert +public class ReservationEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long reservationId; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private UserEntity userEntity; + + @ManyToOne + @JoinColumn(name = "lessondate_id", nullable = false) + private LessonDateEntity lessonDateEntity; + + @ColumnDefault("0") + @Column(nullable = false) + private int applicant; + + @ColumnDefault("0") + @Column(nullable = false) + private boolean isDeleted; + + // 삭제 업데이트 + public void updateIsDeleted(boolean isDeleted) { + this.isDeleted = isDeleted; + } +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/domain/ReservationRepository.java b/src/main/java/com/hanaro/hanafun/reservation/domain/ReservationRepository.java new file mode 100644 index 0000000..e5ba97f --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/domain/ReservationRepository.java @@ -0,0 +1,16 @@ +package com.hanaro.hanafun.reservation.domain; + +import com.hanaro.hanafun.user.domain.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface ReservationRepository extends JpaRepository { + List findReservationEntitiesByUserEntity(UserEntity userEntity); + List findReservationEntitiesByLessonDateEntity_LessondateId(Long lessondateId); + Optional findReservationEntityByUserEntity_UserIdAndLessonDateEntity_LessondateId(Long userId, Long lessondateId); + Optional findByUserEntityUserIdAndLessonDateEntityLessondateId(Long userId, Long lessondateId); +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/request/BookLessonReqDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/request/BookLessonReqDto.java new file mode 100644 index 0000000..271d8a9 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/request/BookLessonReqDto.java @@ -0,0 +1,12 @@ +package com.hanaro.hanafun.reservation.dto.request; + +import lombok.Getter; + +@Getter +public class BookLessonReqDto { + private Long lessondateId; + private Long userId; + private int applicant; // 예약 수량 + private Long accountId; + private String password; // 계좌 비밀번호 확인 +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/request/CancelLessonReqDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/request/CancelLessonReqDto.java new file mode 100644 index 0000000..e93bcd6 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/request/CancelLessonReqDto.java @@ -0,0 +1,8 @@ +package com.hanaro.hanafun.reservation.dto.request; + +import lombok.Getter; + +@Getter +public class CancelLessonReqDto { + private Long reservationId; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/request/LessonDateDetailReqDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/request/LessonDateDetailReqDto.java new file mode 100644 index 0000000..8a41398 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/request/LessonDateDetailReqDto.java @@ -0,0 +1,8 @@ +package com.hanaro.hanafun.reservation.dto.request; + +import lombok.Getter; + +@Getter +public class LessonDateDetailReqDto { + private Long lessondateId; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/request/MyPageReqDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/request/MyPageReqDto.java new file mode 100644 index 0000000..2f41757 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/request/MyPageReqDto.java @@ -0,0 +1,8 @@ +package com.hanaro.hanafun.reservation.dto.request; + +import lombok.Getter; + +@Getter +public class MyPageReqDto { + private Long userId; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/request/MyScheduleReqDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/request/MyScheduleReqDto.java new file mode 100644 index 0000000..08db7fa --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/request/MyScheduleReqDto.java @@ -0,0 +1,10 @@ +package com.hanaro.hanafun.reservation.dto.request; + +import lombok.Getter; + +@Getter +public class MyScheduleReqDto { + private Long userId; + private int year; + private int month; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/response/BookLessonResDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/response/BookLessonResDto.java new file mode 100644 index 0000000..d3f3167 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/response/BookLessonResDto.java @@ -0,0 +1,10 @@ +package com.hanaro.hanafun.reservation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class BookLessonResDto { + private String message; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/response/LessonDateDetailResDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/response/LessonDateDetailResDto.java new file mode 100644 index 0000000..2b36917 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/response/LessonDateDetailResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.reservation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class LessonDateDetailResDto { + private int applicant; // lessondate의 신청인원 + private int capacity; // lesson의 모집인원 + private List people; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/response/MyPageResDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/response/MyPageResDto.java new file mode 100644 index 0000000..a4f7848 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/response/MyPageResDto.java @@ -0,0 +1,13 @@ +package com.hanaro.hanafun.reservation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class MyPageResDto { + private int point; + private List lessons; +} \ No newline at end of file diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/response/MyScheduleResDto.java b/src/main/java/com/hanaro/hanafun/reservation/dto/response/MyScheduleResDto.java new file mode 100644 index 0000000..2c6342c --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/response/MyScheduleResDto.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.reservation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +@Builder +public class MyScheduleResDto { + private Long reservationId; + private Long lessonId; + private LocalDate date; + private String title; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/response/ReservationList.java b/src/main/java/com/hanaro/hanafun/reservation/dto/response/ReservationList.java new file mode 100644 index 0000000..9bb1ef7 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/response/ReservationList.java @@ -0,0 +1,19 @@ +package com.hanaro.hanafun.reservation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +@Builder +public class ReservationList { + private Long reservationId; + private Long lessondateId; + private Long lessonId; + private String image; + private String title; + private String location; + private LocalDate date; + private String categoryName; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/dto/response/ReservationPerson.java b/src/main/java/com/hanaro/hanafun/reservation/dto/response/ReservationPerson.java new file mode 100644 index 0000000..c5fae01 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/dto/response/ReservationPerson.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.reservation.dto.response; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@Builder +public class ReservationPerson { + private LocalDateTime startTime; + private String userName; + private String email; +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/exception/ReservationNotFounException.java b/src/main/java/com/hanaro/hanafun/reservation/exception/ReservationNotFounException.java new file mode 100644 index 0000000..ab1b8bd --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/exception/ReservationNotFounException.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.reservation.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class ReservationNotFounException extends CustomException { + static String MESSAGE = "RESERVATION_NOT_FOUND"; + + public ReservationNotFounException() { + super(MESSAGE); + } + + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/service/ReservationService.java b/src/main/java/com/hanaro/hanafun/reservation/service/ReservationService.java new file mode 100644 index 0000000..1977b85 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/service/ReservationService.java @@ -0,0 +1,26 @@ +package com.hanaro.hanafun.reservation.service; + +import com.hanaro.hanafun.reservation.dto.request.*; +import com.hanaro.hanafun.reservation.dto.response.*; + +import java.util.List; + +public interface ReservationService { + // 마이페이지 데이터 출력 + MyPageResDto myPage(MyPageReqDto myPageReqDto); + + // 나의 신청 클래스 데이터 출력 + List myLessons(MyPageReqDto myPageReqDto); + + // 신청 클래스 일정 데이터 출력 + List mySchedules(MyScheduleReqDto myScheduleReqDto); + + // 개설 클래스 상세- 강좌날짜 별 예약자 정보 출력 + LessonDateDetailResDto lessonDateDetail(LessonDateDetailReqDto lessonDateDetailReqDto); + + // 클래스 예약하기 (결제 제외) + BookLessonResDto bookLesson(BookLessonReqDto bookLessonReqDto); + + // 클래스 취소하기 (환불 제외) + void cancelLesson(CancelLessonReqDto cancelLessonReqDto); +} diff --git a/src/main/java/com/hanaro/hanafun/reservation/service/impl/ReservationServiceImpl.java b/src/main/java/com/hanaro/hanafun/reservation/service/impl/ReservationServiceImpl.java new file mode 100644 index 0000000..ad9dbbd --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/reservation/service/impl/ReservationServiceImpl.java @@ -0,0 +1,237 @@ +package com.hanaro.hanafun.reservation.service.impl; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.account.domain.AccountRepository; +import com.hanaro.hanafun.account.exception.AccountNotFoundException; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import com.hanaro.hanafun.lesson.domain.LessonRepository; +import com.hanaro.hanafun.lessondate.domain.LessonDateEntity; +import com.hanaro.hanafun.lessondate.domain.LessonDateRepository; +import com.hanaro.hanafun.lessondate.exception.LessonDateNotFoundException; +import com.hanaro.hanafun.reservation.domain.ReservationEntity; +import com.hanaro.hanafun.reservation.domain.ReservationRepository; +import com.hanaro.hanafun.reservation.dto.request.*; +import com.hanaro.hanafun.reservation.dto.response.*; +import com.hanaro.hanafun.reservation.exception.ReservationNotFounException; +import com.hanaro.hanafun.reservation.service.ReservationService; +import com.hanaro.hanafun.user.domain.UserEntity; +import com.hanaro.hanafun.user.domain.UserRepository; +import com.hanaro.hanafun.user.exception.UserNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ReservationServiceImpl implements ReservationService { + private final ReservationRepository reservationRepository; + private final UserRepository userRepository; + private final LessonDateRepository lessonDateRepository; + private final AccountRepository accountRepository; + private final LessonRepository lessonRepository; + + // 마이페이지 데이터 출력 + @Transactional + @Override + public MyPageResDto myPage(MyPageReqDto myPageReqDto) { + UserEntity user = userRepository.findById(myPageReqDto.getUserId()).orElseThrow(() -> new UserNotFoundException()); + List reservations = reservationRepository.findReservationEntitiesByUserEntity(user); + + LocalDate today = LocalDate.now(); // 오늘이후 날짜의 예약만 출력 + + List lessons = reservations.stream() + .filter(reservation -> !reservation.getLessonDateEntity().getDate().isBefore(today)) + .map(reservation -> { + LessonDateEntity lessonDate = reservation.getLessonDateEntity(); + LessonEntity lessonEntity = lessonDate.getLessonEntity(); + + ReservationList lesson = ReservationList.builder() + .reservationId(reservation.getReservationId()) + .lessondateId(lessonDate.getLessondateId()) + .lessonId(lessonEntity.getLessonId()) + .image(lessonEntity.getImage()) + .title(lessonEntity.getTitle()) + .location(lessonEntity.getLocation()) + .date(lessonDate.getDate()) + .categoryName(lessonEntity.getCategoryEntity().getCategoryName()) + .build(); + return lesson; + }) + .collect(Collectors.toList()); + + MyPageResDto myPageResDto = MyPageResDto.builder() + .point(user.getPoint()) + .lessons(lessons) + .build(); + + return myPageResDto; + } + + // 나의 신청 클래스 데이터 출력 + @Transactional + @Override + public List myLessons(MyPageReqDto myPageReqDto) { + UserEntity user = userRepository.findById(myPageReqDto.getUserId()).orElseThrow(() -> new UserNotFoundException()); + List reservations = reservationRepository.findReservationEntitiesByUserEntity(user); + + List lessons = reservations.stream() + .map(reservation -> { + LessonDateEntity lessonDate = reservation.getLessonDateEntity(); + LessonEntity lessonEntity = lessonDate.getLessonEntity(); + + ReservationList lesson = ReservationList.builder() + .reservationId(reservation.getReservationId()) + .lessondateId(lessonDate.getLessondateId()) + .lessonId(lessonEntity.getLessonId()) + .image(lessonEntity.getImage()) + .title(lessonEntity.getTitle()) + .location(lessonEntity.getLocation()) + .date(lessonDate.getDate()) + .categoryName(lessonEntity.getCategoryEntity().getCategoryName()) + .build(); + return lesson; + }) + .collect(Collectors.toList()); + + return lessons; + } + + // 신청 클래스 일정 데이터 출력 + @Transactional + @Override + public List mySchedules(MyScheduleReqDto myScheduleReqDto) { + UserEntity user = userRepository.findById(myScheduleReqDto.getUserId()).orElseThrow(() -> new UserNotFoundException()); + List reservations = reservationRepository.findReservationEntitiesByUserEntity(user); + + List mySchedules = reservations.stream() + .filter(reservation -> { + LocalDate date = reservation.getLessonDateEntity().getDate(); + return date.getYear() == myScheduleReqDto.getYear() && date.getMonthValue() == myScheduleReqDto.getMonth(); + }) + .map(reservation -> { + LessonDateEntity lessonDate = reservation.getLessonDateEntity(); + LessonEntity lessonEntity = lessonDate.getLessonEntity(); + + MyScheduleResDto mySchedule = MyScheduleResDto.builder() + .reservationId(reservation.getReservationId()) + .lessonId(lessonEntity.getLessonId()) + .date(lessonDate.getDate()) + .title(lessonEntity.getTitle()) + .build(); + + return mySchedule; + }) + .collect(Collectors.toList()); + + return mySchedules; + } + + // 개설 클래스 상세- 강좌날짜 별 예약자 정보 출력 + @Transactional + @Override + public LessonDateDetailResDto lessonDateDetail(LessonDateDetailReqDto lessonDateDetailReqDto) { + Long lessondateId = lessonDateDetailReqDto.getLessondateId(); + LessonDateEntity lessonDate = lessonDateRepository.findById(lessondateId).orElseThrow(() -> new LessonDateNotFoundException()); + List reservations = reservationRepository.findReservationEntitiesByLessonDateEntity_LessondateId(lessondateId); + + int applicant = lessonDate.getApplicant(); // 신청인원 + int capacity = lessonDate.getLessonEntity().getCapacity(); // 모집인원 + + List people = reservations.stream() + .map(reservation -> { + ReservationPerson person = ReservationPerson.builder() + .startTime(lessonDate.getStartTime()) + .userName(reservation.getUserEntity().getUsername()) + .email(reservation.getUserEntity().getEmail()) + .build(); + return person; + }) + .collect(Collectors.toList()); + + LessonDateDetailResDto lessonDateDetailResDto = LessonDateDetailResDto.builder() + .applicant(applicant) + .capacity(capacity) + .people(people) + .build(); + + return lessonDateDetailResDto; + } + + // 클래스 예약하기 (결제 제외) + @Transactional + @Override + public BookLessonResDto bookLesson(BookLessonReqDto bookLessonReqDto) { + // 계좌 비밀번호 확인 + AccountEntity account = accountRepository.findById(bookLessonReqDto.getAccountId()).orElseThrow(() -> new AccountNotFoundException()); + if (!account.getPassword().equals(bookLessonReqDto.getPassword())) { + return BookLessonResDto.builder() + .message("계좌 비밀번호가 맞지 않습니다.") + .build(); + } + + // 모집인원 초과 확인 + LessonDateEntity lessonDate = lessonDateRepository.findLessonDateEntityByLessondateId(bookLessonReqDto.getLessondateId()).orElseThrow(() -> new LessonDateNotFoundException()); + LessonEntity lesson = lessonDate.getLessonEntity(); + if(lesson.getCapacity() < lessonDate.getApplicant() + bookLessonReqDto.getApplicant()) { + return BookLessonResDto.builder() + .message("모집인원이 초과되었습니다.") + .build(); + } + + // 해당 날짜에 예약 이미 있는지 확인 + Optional existingReservation = reservationRepository.findReservationEntityByUserEntity_UserIdAndLessonDateEntity_LessondateId( + bookLessonReqDto.getUserId(), bookLessonReqDto.getLessondateId()); + if (existingReservation.isPresent()) { + return BookLessonResDto.builder() + .message("이미 예약이 존재합니다.") + .build(); + } + + // 예약 추가 + UserEntity user = userRepository.findUserEntityByUserId(bookLessonReqDto.getUserId()).orElseThrow(() -> new UserNotFoundException()); + ReservationEntity reservation = ReservationEntity.builder() + .userEntity(user) + .lessonDateEntity(lessonDate) + .applicant(bookLessonReqDto.getApplicant()) + .build(); + reservationRepository.save(reservation); + + // 강좌 신청인원 증가 + lessonDate.updateApplicant(lessonDate.getApplicant() + bookLessonReqDto.getApplicant()); + lessonDateRepository.save(lessonDate); + + // 강좌 신청 누적인원 증가 + lesson.updateApplicantSum(lesson.getApplicantSum() + bookLessonReqDto.getApplicant()); + lessonRepository.save(lesson); + + return BookLessonResDto.builder() + .message("예약완료") + .build(); + } + + // 클래스 취소하기 (환불 제외) + @Transactional + @Override + public void cancelLesson(CancelLessonReqDto cancelLessonReqDto) { + ReservationEntity reservation = reservationRepository.findById(cancelLessonReqDto.getReservationId()).orElseThrow(() -> new ReservationNotFounException()); + + // 클래스 취소 (논리적 삭제) + reservation.updateIsDeleted(true); + reservationRepository.save(reservation); + + // 신청 인원 제거 + LessonDateEntity lessonDate = reservation.getLessonDateEntity(); + lessonDate.updateApplicant(lessonDate.getApplicant() - reservation.getApplicant()); + lessonDateRepository.save(lessonDate); + + // 강좌 신청 누적인원 제거 + LessonEntity lesson = lessonDate.getLessonEntity(); + lesson.updateApplicantSum(lesson.getApplicantSum() - reservation.getApplicant()); + lessonRepository.save(lesson); + } +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/controller/RevenueController.java b/src/main/java/com/hanaro/hanafun/revenue/controller/RevenueController.java new file mode 100644 index 0000000..9533a24 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/controller/RevenueController.java @@ -0,0 +1,41 @@ +package com.hanaro.hanafun.revenue.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.revenue.dto.*; +import com.hanaro.hanafun.revenue.service.impl.RevenueServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/revenue") +public class RevenueController { + private final RevenueServiceImpl revenueService; + @GetMapping("/total") + public ResponseEntity totalRevenue(@AuthenticationPrincipal Long userId){ + TotalRevenueResDto totalRevenueResDto = revenueService.totalRevenue(userId); + return ResponseEntity.ok(new ApiResponse(true, "ok", totalRevenueResDto)); + } + + @GetMapping("/{year}/{month}") + public ResponseEntity monthRevenue(@AuthenticationPrincipal Long userId, @PathVariable Integer year, @PathVariable Integer month){ + List monthRevenueResDtoList = revenueService.monthRevenue(userId, year, month); + return ResponseEntity.ok(new ApiResponse(true, "ok", monthRevenueResDtoList)); + } + + @GetMapping("/lesson/{year}/{lessonId}") + public ResponseEntity lessonRevenue(@PathVariable Integer year, @PathVariable Long lessonId){ + List lessonRevenueResDtoList = revenueService.lessonRevenue(year, lessonId); + return ResponseEntity.ok(new ApiResponse(true, "ok", lessonRevenueResDtoList)); + } + + @PutMapping("/update") + public ResponseEntity updatePrice(@RequestBody UpdatePriceReqDto updatePriceReqDto){ + UpdatePriceResDto updatePriceResDto = revenueService.updatePrice(updatePriceReqDto); + return ResponseEntity.ok(new ApiResponse(true, "ok", updatePriceResDto)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/domain/RevenueEntity.java b/src/main/java/com/hanaro/hanafun/revenue/domain/RevenueEntity.java new file mode 100644 index 0000000..29ebc5d --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/domain/RevenueEntity.java @@ -0,0 +1,40 @@ +package com.hanaro.hanafun.revenue.domain; + +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; + +@Entity +@Table(name = "revenue") +@Data +@EqualsAndHashCode(callSuper = false) +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RevenueEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "revenue_id") + private Long revenueId; + + @ManyToOne + @JoinColumn(name = "lesson_id") + private LessonEntity lessonEntity; + + @Column(name = "revenue", nullable = false) + private Long revenue; + + @Column(name = "material_price", nullable = false) + @ColumnDefault("0") + private Integer materialPrice; + + @Column(name = "rental_price", nullable = false) + @ColumnDefault("0") + private Integer rentalPrice; + + @Column(name = "etc_price", nullable = false) + @ColumnDefault("0") + private Integer etcPrice; +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/domain/RevenueRepository.java b/src/main/java/com/hanaro/hanafun/revenue/domain/RevenueRepository.java new file mode 100644 index 0000000..42b5f04 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/domain/RevenueRepository.java @@ -0,0 +1,20 @@ +package com.hanaro.hanafun.revenue.domain; + +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface RevenueRepository extends JpaRepository { + Optional findByCreatedDateBetween(LocalDateTime startDateTime, LocalDateTime endDateTime); + + @Query(value = "SELECT SUM(R.revenue) FROM RevenueEntity R WHERE R.lessonEntity = :lessonEntity") + Long totalRevenueByLessonId(@Param("lessonEntity") LessonEntity lessonEntity); + + Optional findByLessonEntityAndCreatedDateBetween(LessonEntity lessonEntity, LocalDateTime startDateTime, LocalDateTime endDateTime); + Optional> findByLessonEntityLessonIdAndCreatedDateBetween(Long lessonId, LocalDateTime startDateTime, LocalDateTime endDateTime); +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/dto/LessonRevenueResDto.java b/src/main/java/com/hanaro/hanafun/revenue/dto/LessonRevenueResDto.java new file mode 100644 index 0000000..433a6a2 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/dto/LessonRevenueResDto.java @@ -0,0 +1,20 @@ +package com.hanaro.hanafun.revenue.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LessonRevenueResDto { + private Integer month; + private Long lessonId; + private String title; + private Long revenue; + private Integer materialPrice; + private Integer rentalPrice; + private Integer etcPrice; +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/dto/MonthRevenueResDto.java b/src/main/java/com/hanaro/hanafun/revenue/dto/MonthRevenueResDto.java new file mode 100644 index 0000000..6fd0b7f --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/dto/MonthRevenueResDto.java @@ -0,0 +1,18 @@ +package com.hanaro.hanafun.revenue.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MonthRevenueResDto { + + private Long lessonId; + private String title; + private Long revenue; + +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/dto/TotalRevenueResDto.java b/src/main/java/com/hanaro/hanafun/revenue/dto/TotalRevenueResDto.java new file mode 100644 index 0000000..b74ef53 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/dto/TotalRevenueResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.revenue.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TotalRevenueResDto { + private long yearRevenue; +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/dto/UpdatePriceReqDto.java b/src/main/java/com/hanaro/hanafun/revenue/dto/UpdatePriceReqDto.java new file mode 100644 index 0000000..0ac1bfa --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/dto/UpdatePriceReqDto.java @@ -0,0 +1,19 @@ +package com.hanaro.hanafun.revenue.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UpdatePriceReqDto { + private Long lessonId; + private Integer year; + private Integer month; + private Integer materialPrice; + private Integer rentalPrice; + private Integer etcPrice; +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/dto/UpdatePriceResDto.java b/src/main/java/com/hanaro/hanafun/revenue/dto/UpdatePriceResDto.java new file mode 100644 index 0000000..788edbf --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/dto/UpdatePriceResDto.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.revenue.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UpdatePriceResDto { + private Long lessonId; + private Integer materialPrice; + private Integer rentalPrice; + private Integer etcPrice; +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/mapper/RevenueMapper.java b/src/main/java/com/hanaro/hanafun/revenue/mapper/RevenueMapper.java new file mode 100644 index 0000000..c289f23 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/mapper/RevenueMapper.java @@ -0,0 +1,33 @@ +package com.hanaro.hanafun.revenue.mapper; + +import com.hanaro.hanafun.revenue.domain.RevenueEntity; +import com.hanaro.hanafun.revenue.dto.LessonRevenueResDto; +import com.hanaro.hanafun.revenue.dto.MonthRevenueResDto; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class RevenueMapper { + public static MonthRevenueResDto revenueEntityToMonthRevenueDto(RevenueEntity revenueEntity){ + if(revenueEntity == null) return null; + + return MonthRevenueResDto.builder() + .lessonId(revenueEntity.getLessonEntity().getLessonId()) + .title(revenueEntity.getLessonEntity().getTitle()) + .revenue(revenueEntity.getRevenue()) + .build(); + } + + public static LessonRevenueResDto revenueEntityToLessonRevenueResDto(RevenueEntity revenueEntity){ + if(revenueEntity == null) return null; + + return LessonRevenueResDto.builder() + .month(revenueEntity.getCreatedDate().getMonthValue()) + .lessonId(revenueEntity.getLessonEntity().getLessonId()) + .title(revenueEntity.getLessonEntity().getTitle()) + .revenue(revenueEntity.getRevenue()) + .materialPrice(revenueEntity.getMaterialPrice()) + .rentalPrice(revenueEntity.getRentalPrice()) + .etcPrice(revenueEntity.getEtcPrice()) + .build(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/service/RevenueService.java b/src/main/java/com/hanaro/hanafun/revenue/service/RevenueService.java new file mode 100644 index 0000000..f4388a0 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/service/RevenueService.java @@ -0,0 +1,12 @@ +package com.hanaro.hanafun.revenue.service; + +import com.hanaro.hanafun.revenue.dto.*; + +import java.util.List; + +public interface RevenueService { + TotalRevenueResDto totalRevenue(Long userId); + List monthRevenue(Long userId, Integer year, Integer month); + List lessonRevenue(Integer year, Long lessonId); + UpdatePriceResDto updatePrice(UpdatePriceReqDto updatePriceReqDto); +} diff --git a/src/main/java/com/hanaro/hanafun/revenue/service/impl/RevenueServiceImpl.java b/src/main/java/com/hanaro/hanafun/revenue/service/impl/RevenueServiceImpl.java new file mode 100644 index 0000000..d55be5e --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/revenue/service/impl/RevenueServiceImpl.java @@ -0,0 +1,99 @@ +package com.hanaro.hanafun.revenue.service.impl; + +import com.hanaro.hanafun.host.domain.HostEntity; +import com.hanaro.hanafun.host.domain.HostRepository; +import com.hanaro.hanafun.host.exception.HostNotFoundException; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import com.hanaro.hanafun.lesson.domain.LessonRepository; +import com.hanaro.hanafun.revenue.domain.RevenueEntity; +import com.hanaro.hanafun.revenue.domain.RevenueRepository; +import com.hanaro.hanafun.revenue.dto.*; +import com.hanaro.hanafun.revenue.mapper.RevenueMapper; +import com.hanaro.hanafun.revenue.service.RevenueService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.Year; +import java.time.YearMonth; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RevenueServiceImpl implements RevenueService { + private final HostRepository hostRepository; + private final LessonRepository lessonRepository; + private final RevenueRepository revenueRepository; + + @Override + @Transactional + public TotalRevenueResDto totalRevenue(Long userId) { + Long yearRevenue; + HostEntity hostEntity = hostRepository.findByUserEntityUserId(userId).orElseThrow(() -> new HostNotFoundException()); + + List lessonEntityList = lessonRepository.findByHostEntityHostId(hostEntity.getHostId()).orElseThrow(); + yearRevenue = lessonEntityList.stream().mapToLong(lessonEntity -> revenueRepository.totalRevenueByLessonId(lessonEntity)).sum(); + + return new TotalRevenueResDto().builder() + .yearRevenue(yearRevenue) + .build(); + } + + @Override + @Transactional + public List monthRevenue(Long userId, Integer year, Integer month) { + HostEntity hostEntity = hostRepository.findByUserEntityUserId(userId).orElseThrow(() -> new HostNotFoundException()); + List lessonEntityList = lessonRepository.findByHostEntityHostId(hostEntity.getHostId()).orElseThrow(); + + YearMonth yearMonth = YearMonth.of(year, month); + LocalDateTime startOfMonth = yearMonth.atDay(1).atStartOfDay(); + LocalDateTime endOfMonth = yearMonth.atEndOfMonth().atTime(23, 59, 59); + + List revenueEntityList = lessonEntityList.stream() + .flatMap(lessonEntity -> revenueRepository.findByLessonEntityAndCreatedDateBetween(lessonEntity, startOfMonth, endOfMonth).stream()) + .collect(Collectors.toList()); + + return revenueEntityList.stream() + .map(RevenueMapper::revenueEntityToMonthRevenueDto) + .toList(); + } + + @Override + @Transactional + public List lessonRevenue(Integer year, Long lessonId) { + Year searchYear = Year.of(year); + LocalDateTime startOfYear = searchYear.atMonth(1).atDay(1).atStartOfDay(); + LocalDateTime endOfYear = searchYear.atMonth(12).atEndOfMonth().atTime(23,59,59); + + List revenueEntityList = revenueRepository.findByLessonEntityLessonIdAndCreatedDateBetween(lessonId, startOfYear, endOfYear).orElseThrow(); + + return revenueEntityList.stream() + .map(RevenueMapper::revenueEntityToLessonRevenueResDto) + .toList(); + } + + @Override + @Transactional + public UpdatePriceResDto updatePrice(UpdatePriceReqDto updatePriceReqDto) { + LessonEntity lessonEntity = lessonRepository.findById(updatePriceReqDto.getLessonId()).orElseThrow(); + + YearMonth yearMonth = YearMonth.of(updatePriceReqDto.getYear(), updatePriceReqDto.getMonth()); + LocalDateTime startOfMonth = yearMonth.atDay(1).atStartOfDay(); + LocalDateTime endOfMonth = yearMonth.atEndOfMonth().atTime(23, 59, 59); + + RevenueEntity revenueEntity = revenueRepository.findByLessonEntityAndCreatedDateBetween(lessonEntity, startOfMonth, endOfMonth).orElseThrow(); + revenueEntity.setMaterialPrice(updatePriceReqDto.getMaterialPrice()); + revenueEntity.setRentalPrice(updatePriceReqDto.getRentalPrice()); + revenueEntity.setEtcPrice(updatePriceReqDto.getEtcPrice()); + + return new UpdatePriceResDto().builder() + .lessonId(revenueEntity.getLessonEntity().getLessonId()) + .materialPrice(revenueEntity.getMaterialPrice()) + .rentalPrice(revenueEntity.getRentalPrice()) + .etcPrice(revenueEntity.getEtcPrice()) + .build(); + } +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/controller/TransactionController.java b/src/main/java/com/hanaro/hanafun/transaction/controller/TransactionController.java new file mode 100644 index 0000000..b66d1a7 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/controller/TransactionController.java @@ -0,0 +1,43 @@ +package com.hanaro.hanafun.transaction.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.transaction.dto.PayResDto; +import com.hanaro.hanafun.transaction.dto.PaybackReqDto; +import com.hanaro.hanafun.transaction.dto.QrReqDto; +import com.hanaro.hanafun.transaction.dto.SimpleReqDto; +import com.hanaro.hanafun.transaction.service.impl.TransactionServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/transaction") +public class TransactionController { + private final TransactionServiceImpl transactionService; + + @PostMapping("/qr") + public ResponseEntity qrPay(@RequestBody QrReqDto qrReqDto){ + PayResDto payResDto = transactionService.qrPay(qrReqDto); + return ResponseEntity.ok(new ApiResponse(true, "ok", payResDto)); + } + + @PostMapping("/simple") + public ResponseEntity simplePay(@AuthenticationPrincipal Long userId, @RequestBody SimpleReqDto simpleReqDto){ + PayResDto payResDto = transactionService.simplePay(userId, simpleReqDto); + return ResponseEntity.ok(new ApiResponse(true, "ok", payResDto)); + } + + @PostMapping("/payback") + public ResponseEntity payback(@AuthenticationPrincipal Long userId, @RequestBody PaybackReqDto paybackReqDto){ + PayResDto payResDto = transactionService.payback(userId, paybackReqDto); + return ResponseEntity.ok(new ApiResponse(true, "ok", payResDto)); + } + + @PostMapping("/test") + public ResponseEntity test(){ + transactionService.autoTransfer(); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", null)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/domain/TransactionEntity.java b/src/main/java/com/hanaro/hanafun/transaction/domain/TransactionEntity.java new file mode 100644 index 0000000..b1fef8a --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/domain/TransactionEntity.java @@ -0,0 +1,44 @@ +package com.hanaro.hanafun.transaction.domain; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.common.domain.BaseEntity; +import com.hanaro.hanafun.reservation.domain.ReservationEntity; +import com.hanaro.hanafun.transaction.enums.Type; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "transaction") +@Data +@EqualsAndHashCode(callSuper = false) +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TransactionEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "transaction_id") + private Long transactionId; + + @ManyToOne + @JoinColumn(name = "deposit_id", nullable = false) + private AccountEntity depositAccount; + + @ManyToOne + @JoinColumn(name = "withdraw_id", nullable = false) + private AccountEntity withdrawAccount; + + @ManyToOne + @JoinColumn(name = "reservation_id", nullable = false) + private ReservationEntity reservationEntity; + + @Column(name = "payment", nullable = false) + private Integer payment; + + @Column(name = "point", nullable = false) + private Integer point; + + @Column(name = "type", nullable = false) + @Enumerated(EnumType.STRING) + private Type type; +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/domain/TransactionRepository.java b/src/main/java/com/hanaro/hanafun/transaction/domain/TransactionRepository.java new file mode 100644 index 0000000..97e634b --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/domain/TransactionRepository.java @@ -0,0 +1,10 @@ +package com.hanaro.hanafun.transaction.domain; + +import com.hanaro.hanafun.transaction.enums.Type; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface TransactionRepository extends JpaRepository { + Optional findByReservationEntityReservationIdAndType(Long reservationId, Type type); +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/dto/PayResDto.java b/src/main/java/com/hanaro/hanafun/transaction/dto/PayResDto.java new file mode 100644 index 0000000..6e30cb5 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/dto/PayResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PayResDto { + private long transactionId; +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/dto/PaybackReqDto.java b/src/main/java/com/hanaro/hanafun/transaction/dto/PaybackReqDto.java new file mode 100644 index 0000000..06bc3a2 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/dto/PaybackReqDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PaybackReqDto { + private long reservationId; +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/dto/QrReqDto.java b/src/main/java/com/hanaro/hanafun/transaction/dto/QrReqDto.java new file mode 100644 index 0000000..803aebb --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/dto/QrReqDto.java @@ -0,0 +1,18 @@ +package com.hanaro.hanafun.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class QrReqDto { + private long withdrawId; + private long depositId; + private long lessonId; + private long lessondateId; + private int payment; +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/dto/SimpleReqDto.java b/src/main/java/com/hanaro/hanafun/transaction/dto/SimpleReqDto.java new file mode 100644 index 0000000..1901727 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/dto/SimpleReqDto.java @@ -0,0 +1,18 @@ +package com.hanaro.hanafun.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SimpleReqDto { + private long withdrawId; + private long lessondateId; + private long reservationId; + private int payment; + private int point; +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/enums/Type.java b/src/main/java/com/hanaro/hanafun/transaction/enums/Type.java new file mode 100644 index 0000000..cf38380 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/enums/Type.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.transaction.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Type { + QR("QR"), + PENDING("PENDING"), + COMPLETED("COMPLETED"), + CANCELED("CANCELED"); + + private String value; +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/service/TransactionService.java b/src/main/java/com/hanaro/hanafun/transaction/service/TransactionService.java new file mode 100644 index 0000000..734bae3 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/service/TransactionService.java @@ -0,0 +1,12 @@ +package com.hanaro.hanafun.transaction.service; + +import com.hanaro.hanafun.transaction.dto.PayResDto; +import com.hanaro.hanafun.transaction.dto.PaybackReqDto; +import com.hanaro.hanafun.transaction.dto.QrReqDto; +import com.hanaro.hanafun.transaction.dto.SimpleReqDto; + +public interface TransactionService { + PayResDto qrPay(QrReqDto qrReqDto); + PayResDto simplePay(Long userId, SimpleReqDto simpleReqDto); + PayResDto payback(Long userId, PaybackReqDto paybackReqDto); +} diff --git a/src/main/java/com/hanaro/hanafun/transaction/service/impl/TransactionServiceImpl.java b/src/main/java/com/hanaro/hanafun/transaction/service/impl/TransactionServiceImpl.java new file mode 100644 index 0000000..9b74f56 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/transaction/service/impl/TransactionServiceImpl.java @@ -0,0 +1,218 @@ +package com.hanaro.hanafun.transaction.service.impl; + +import com.hanaro.hanafun.account.domain.AccountEntity; +import com.hanaro.hanafun.account.domain.AccountRepository; +import com.hanaro.hanafun.account.exception.AccountBalanceException; +import com.hanaro.hanafun.account.exception.AccountNotFoundException; +import com.hanaro.hanafun.hanastorage.domain.HanastorageEntity; +import com.hanaro.hanafun.hanastorage.domain.HanastorageRepository; +import com.hanaro.hanafun.host.domain.HostEntity; +import com.hanaro.hanafun.lesson.domain.LessonEntity; +import com.hanaro.hanafun.lesson.domain.LessonRepository; +import com.hanaro.hanafun.lessondate.domain.LessonDateEntity; +import com.hanaro.hanafun.lessondate.domain.LessonDateRepository; +import com.hanaro.hanafun.reservation.domain.ReservationRepository; +import com.hanaro.hanafun.revenue.domain.RevenueEntity; +import com.hanaro.hanafun.revenue.domain.RevenueRepository; +import com.hanaro.hanafun.transaction.domain.TransactionEntity; +import com.hanaro.hanafun.transaction.domain.TransactionRepository; +import com.hanaro.hanafun.transaction.dto.PayResDto; +import com.hanaro.hanafun.transaction.dto.PaybackReqDto; +import com.hanaro.hanafun.transaction.dto.QrReqDto; +import com.hanaro.hanafun.transaction.dto.SimpleReqDto; +import com.hanaro.hanafun.transaction.enums.Type; +import com.hanaro.hanafun.transaction.service.TransactionService; +import com.hanaro.hanafun.user.domain.UserEntity; +import com.hanaro.hanafun.user.domain.UserRepository; +import com.hanaro.hanafun.user.exception.UserNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class TransactionServiceImpl implements TransactionService { + private final UserRepository userRepository; + private final LessonRepository lessonRepository; + private final LessonDateRepository lessonDateRepository; + private final ReservationRepository reservationRepository; + private final RevenueRepository revenueRepository; + private final AccountRepository accountRepository; + private final HanastorageRepository hanastorageRepository; + private final TransactionRepository transactionRepository; + + static private String PLUS = "PLUS"; + static private String MINUS = "MINUS"; + static private String ZERO = "ZERO"; + + @Override + @Transactional + public PayResDto qrPay(QrReqDto qrReqDto) { + //계좌 업데이트 + AccountEntity withdrawAccount = calcAccount(qrReqDto.getWithdrawId(), qrReqDto.getPayment(), MINUS); + AccountEntity depositAccount = calcAccount(qrReqDto.getDepositId(), qrReqDto.getPayment(), PLUS); + + //매출 업데이트 + calcRevenue(qrReqDto.getLessonId(), qrReqDto.getPayment()); + + //게스트 id 건지기 + AccountEntity accountEntity = accountRepository.findById(qrReqDto.getWithdrawId()).orElseThrow(() -> new AccountNotFoundException()); + Long guestId = accountEntity.getUserEntity().getUserId(); + + //거래 내역 저장 + TransactionEntity transactionEntity = TransactionEntity.builder() + .depositAccount(depositAccount) + .withdrawAccount(withdrawAccount) + .reservationEntity(reservationRepository + .findByUserEntityUserIdAndLessonDateEntityLessondateId(guestId, qrReqDto.getLessondateId()) + .get()) + .payment(qrReqDto.getPayment()) + .point(0) + .type(Type.QR) + .build(); + TransactionEntity createdTransaction = transactionRepository.save(transactionEntity); + + return new PayResDto().builder() + .transactionId(createdTransaction.getTransactionId()) + .build(); + } + + @Override + @Transactional + public PayResDto simplePay(Long userId, SimpleReqDto simpleReqDto) { + //호스트 계좌번호 가져오기 + LessonDateEntity lessonDateEntity = lessonDateRepository.findById(simpleReqDto.getLessondateId()) + .orElse(null); + Long depositId = lessonDateEntity.getLessonEntity().getHostEntity().getAccountEntity().getAccountId(); + + //계좌 잔액 업데이트 + AccountEntity withdrawAccount = calcAccount(simpleReqDto.getWithdrawId(), simpleReqDto.getPayment() - simpleReqDto.getPoint(), MINUS); + AccountEntity depositAccount = calcAccount(depositId, 0, ZERO); + + //게스트 포인트 소멸 + UserEntity userEntity = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException()); + userEntity.setPoint(userEntity.getPoint() - simpleReqDto.getPoint()); + + //거래 내역 저장 _ PENDING + TransactionEntity transactionEntity = TransactionEntity.builder() + .depositAccount(depositAccount) + .withdrawAccount(withdrawAccount) + .reservationEntity(reservationRepository.findById(simpleReqDto.getReservationId()).get()) + .payment(simpleReqDto.getPayment()) + .point(simpleReqDto.getPoint()) + .type(Type.PENDING) + .build(); + TransactionEntity createdTransaction = transactionRepository.save(transactionEntity); + + //하나은행 저장소 저장 + HanastorageEntity hanastorageEntity = HanastorageEntity.builder() + .transactionEntity(createdTransaction) + .lessondate(lessonDateEntity.getDate()) + .isDeleted(false) + .build(); + hanastorageRepository.save(hanastorageEntity); + + return new PayResDto().builder() + .transactionId(createdTransaction.getTransactionId()) + .build(); + } + + @Override + @Transactional + public PayResDto payback(Long userId, PaybackReqDto paybackReqDto) { + //거래에서 거래 타입 변경 + TransactionEntity transactionEntity = transactionRepository.findByReservationEntityReservationIdAndType(paybackReqDto.getReservationId(), Type.PENDING).orElseThrow(); + transactionEntity.setType(Type.CANCELED); + + //하나은행 저장소 삭제 처리 + HanastorageEntity hanastorageEntity = hanastorageRepository.findByTransactionEntityTransactionId(transactionEntity.getTransactionId()).orElseThrow(); + hanastorageEntity.setIsDeleted(true); + + //게스트 계좌에 송금 + Long guestAccountId = transactionEntity.getWithdrawAccount().getAccountId(); + calcAccount(guestAccountId, transactionEntity.getPayment() - transactionEntity.getPoint(), PLUS); + + //게스트 포인트 적립 + UserEntity userEntity = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException()); + userEntity.setPoint(userEntity.getPoint() + transactionEntity.getPoint()); + + return new PayResDto().builder() + .transactionId(transactionEntity.getTransactionId()) + .build(); + } + + @Transactional + public void autoTransfer(){ + LocalDate yesterday = LocalDate.now().minusDays(1); + List hanastorageEntityList = hanastorageRepository.findByLessondateAndIsDeleted(yesterday, false); + hanastorageEntityList.forEach(this::doAutoTransfer); + } + + @Transactional + private void doAutoTransfer(HanastorageEntity hanastorageEntity){ + //하나은행 저장소 삭제 처리 + hanastorageEntity.setIsDeleted(true); + + //거래에서 거래 타입 변경 + TransactionEntity transactionEntity = transactionRepository.findById(hanastorageEntity.getTransactionEntity().getTransactionId()) + .orElse(null); + transactionEntity.setType(Type.COMPLETED); + + //호스트 계좌에 송금 + Long hostAccountId = transactionEntity.getDepositAccount().getAccountId(); + calcAccount(hostAccountId, transactionEntity.getPayment(), PLUS); + + //매출 업데이트 + Long lessonId = transactionEntity + .getReservationEntity() + .getLessonDateEntity() + .getLessonEntity() + .getLessonId(); + calcRevenue(lessonId, transactionEntity.getPayment()); + } + + @Transactional + private AccountEntity calcAccount(Long accountId, int payment, String type){ + AccountEntity accountEntity = accountRepository.findById(accountId) + .orElseThrow(() -> new AccountNotFoundException()); + + if(type.equals(PLUS)){ + accountEntity.setBalance(accountEntity.getBalance() + payment); + } else if(type.equals(MINUS)) { + if(accountEntity.getBalance() < payment){ + throw new AccountBalanceException(); + } + accountEntity.setBalance(accountEntity.getBalance() - payment); + } + + return accountEntity; + } + + @Transactional + private void calcRevenue(Long lessonId, int payment){ + LocalDateTime startOfMonth = YearMonth.now().atDay(1).atStartOfDay(); + LocalDateTime endOfMonth = YearMonth.now().atEndOfMonth().atTime(23, 59, 59); + + RevenueEntity revenueEntity = revenueRepository.findByCreatedDateBetween(startOfMonth, endOfMonth) + .orElse(null); + + if (revenueEntity != null) { + revenueEntity.setRevenue(revenueEntity.getRevenue() + payment); + } else { + RevenueEntity newRevenue = RevenueEntity.builder() + .lessonEntity(lessonRepository.findById(lessonId).get()) + .revenue((long) payment) + .materialPrice(0) + .rentalPrice(0) + .etcPrice(0) + .build(); + revenueRepository.save(newRevenue); + } + } +} diff --git a/src/main/java/com/hanaro/hanafun/user/controller/UserController.java b/src/main/java/com/hanaro/hanafun/user/controller/UserController.java new file mode 100644 index 0000000..0345af9 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/controller/UserController.java @@ -0,0 +1,39 @@ +package com.hanaro.hanafun.user.controller; + +import com.hanaro.hanafun.common.dto.ApiResponse; +import com.hanaro.hanafun.user.dto.IsHostResDto; +import com.hanaro.hanafun.user.dto.LoginReqDto; +import com.hanaro.hanafun.user.dto.LoginResDto; +import com.hanaro.hanafun.user.dto.PointResDto; +import com.hanaro.hanafun.user.service.impl.UserServiceImpl; +import lombok.RequiredArgsConstructor; +import org.apache.coyote.Response; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/user") +public class UserController { + + private final UserServiceImpl userService; + + @PostMapping("/login") + ResponseEntity login(@RequestBody LoginReqDto request) { + LoginResDto loginResDto = userService.login(request); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", loginResDto)); + } + + @GetMapping("/point") + ResponseEntity readPoint(@AuthenticationPrincipal Long userId){ + PointResDto pointResDto = userService.readPoint(userId); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", pointResDto)); + } + + @GetMapping("/isHost") + ResponseEntity readIsHost(@AuthenticationPrincipal Long userId){ + IsHostResDto isHostResDto = userService.readIsHost(userId); + return ResponseEntity.ok(new ApiResponse<>(true, "ok", isHostResDto)); + } +} diff --git a/src/main/java/com/hanaro/hanafun/user/domain/UserEntity.java b/src/main/java/com/hanaro/hanafun/user/domain/UserEntity.java new file mode 100644 index 0000000..0cd30ec --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/domain/UserEntity.java @@ -0,0 +1,35 @@ +package com.hanaro.hanafun.user.domain; + +import com.hanaro.hanafun.common.domain.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="user") +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserEntity extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="user_id") + private Long userId; + + @Column(name="username", nullable = false) + private String username; + + @Column(name="password", length = 20, nullable = false) + private String password; + + @Column(name = "point", nullable = false) + private Integer point; + + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "is_host", nullable = false) + private Boolean isHost; + + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted; +} diff --git a/src/main/java/com/hanaro/hanafun/user/domain/UserRepository.java b/src/main/java/com/hanaro/hanafun/user/domain/UserRepository.java new file mode 100644 index 0000000..2d7a390 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/domain/UserRepository.java @@ -0,0 +1,12 @@ +package com.hanaro.hanafun.user.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByPassword(String password); + Optional findUserEntityByUserId(Long userId); +} diff --git a/src/main/java/com/hanaro/hanafun/user/dto/IsHostResDto.java b/src/main/java/com/hanaro/hanafun/user/dto/IsHostResDto.java new file mode 100644 index 0000000..19bd91c --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/dto/IsHostResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class IsHostResDto { + private Boolean isHost; +} diff --git a/src/main/java/com/hanaro/hanafun/user/dto/LoginReqDto.java b/src/main/java/com/hanaro/hanafun/user/dto/LoginReqDto.java new file mode 100644 index 0000000..aa0aa70 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/dto/LoginReqDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LoginReqDto { + private String password; +} diff --git a/src/main/java/com/hanaro/hanafun/user/dto/LoginResDto.java b/src/main/java/com/hanaro/hanafun/user/dto/LoginResDto.java new file mode 100644 index 0000000..675a57c --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/dto/LoginResDto.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LoginResDto { + private String jwt; + private String userName; +} diff --git a/src/main/java/com/hanaro/hanafun/user/dto/PointResDto.java b/src/main/java/com/hanaro/hanafun/user/dto/PointResDto.java new file mode 100644 index 0000000..e4f8eee --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/dto/PointResDto.java @@ -0,0 +1,14 @@ +package com.hanaro.hanafun.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PointResDto { + private Integer point; +} diff --git a/src/main/java/com/hanaro/hanafun/user/exception/UserNotFoundException.java b/src/main/java/com/hanaro/hanafun/user/exception/UserNotFoundException.java new file mode 100644 index 0000000..90e1c59 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/exception/UserNotFoundException.java @@ -0,0 +1,17 @@ +package com.hanaro.hanafun.user.exception; + +import com.hanaro.hanafun.common.exception.CustomException; +import org.springframework.http.HttpStatus; + +public class UserNotFoundException extends CustomException { + static String MESSAGE = "USER_NOT_FOUND"; + + public UserNotFoundException() { + super(MESSAGE); + } + + @Override + public HttpStatus getHttpStatus() { + return HttpStatus.NOT_FOUND; + } +} diff --git a/src/main/java/com/hanaro/hanafun/user/service/UserService.java b/src/main/java/com/hanaro/hanafun/user/service/UserService.java new file mode 100644 index 0000000..1b9ecba --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/service/UserService.java @@ -0,0 +1,15 @@ +package com.hanaro.hanafun.user.service; + +import com.hanaro.hanafun.user.dto.IsHostResDto; +import com.hanaro.hanafun.user.dto.LoginReqDto; +import com.hanaro.hanafun.user.dto.LoginResDto; +import com.hanaro.hanafun.user.dto.PointResDto; + +public interface UserService { + LoginResDto login(LoginReqDto loginReqDto); + + PointResDto readPoint(Long userId); + + IsHostResDto readIsHost(Long userId); + +} diff --git a/src/main/java/com/hanaro/hanafun/user/service/impl/UserServiceImpl.java b/src/main/java/com/hanaro/hanafun/user/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..c260e86 --- /dev/null +++ b/src/main/java/com/hanaro/hanafun/user/service/impl/UserServiceImpl.java @@ -0,0 +1,53 @@ +package com.hanaro.hanafun.user.service.impl; + +import com.hanaro.hanafun.common.authentication.JwtUtil; +import com.hanaro.hanafun.user.domain.UserEntity; +import com.hanaro.hanafun.user.domain.UserRepository; +import com.hanaro.hanafun.user.dto.IsHostResDto; +import com.hanaro.hanafun.user.dto.LoginReqDto; +import com.hanaro.hanafun.user.dto.LoginResDto; +import com.hanaro.hanafun.user.dto.PointResDto; +import com.hanaro.hanafun.user.exception.UserNotFoundException; +import com.hanaro.hanafun.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + private final JwtUtil jwtUtil; + + @Override + public LoginResDto login(LoginReqDto loginReqDto){ + UserEntity userEntity = userRepository.findByPassword(loginReqDto.getPassword()) + .orElseThrow(() -> new UserNotFoundException()); + String generatedJwt = jwtUtil.createToken(userEntity.getUsername(), userEntity.getUserId()); + + return new LoginResDto().builder() + .jwt(generatedJwt) + .userName(userEntity.getUsername()) + .build(); + } + + @Override + public PointResDto readPoint(Long userId){ + UserEntity userEntity = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException()); + + return new PointResDto().builder() + .point(userEntity.getPoint()) + .build(); + } + + @Override + public IsHostResDto readIsHost(Long userId){ + UserEntity userEntity = userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException()); + + return new IsHostResDto().builder() + .isHost(userEntity.getIsHost()) + .build(); + } +} diff --git a/src/main/resources/db.sql b/src/main/resources/db.sql new file mode 100644 index 0000000..1cac377 --- /dev/null +++ b/src/main/resources/db.sql @@ -0,0 +1,135 @@ +use hanafun; + +CREATE TABLE `user` ( + `user_id` BIGINT NOT NULL AUTO_INCREMENT, + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(20) NOT NULL, + `point` INT NOT NULL DEFAULT 0, + `email` VARCHAR(255) NOT NULL, + `is_host` BOOLEAN NOT NULL DEFAULT FALSE, + `is_deleted` BOOLEAN NOT NULL DEFAULT FALSE, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`user_id`) +); + +CREATE TABLE `account` ( + `account_id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `account_number` VARCHAR(20) NOT NULL, + `account_name` VARCHAR(50) NOT NULL, + `password` INT NOT NULL, + `balance` INT NOT NULL DEFAULT 0, + `qr` VARCHAR(255) NULL, + `is_deleted` BOOLEAN NOT NULL DEFAULT FALSE, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`account_id`), + FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) +); + +CREATE TABLE `host` ( + `host_id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL, + `introduction` VARCHAR(255) NULL, + `account_id` BIGINT NOT NULL, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`host_id`), + FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`), + FOREIGN KEY (`account_id`) REFERENCES `account` (`account_id`) +); + +CREATE TABLE `category` ( + `category_id` BIGINT NOT NULL AUTO_INCREMENT, + `category_name` VARCHAR(255) NOT NULL, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`category_id`) +); + +CREATE TABLE `lesson` ( + `lesson_id` BIGINT NOT NULL AUTO_INCREMENT, + `host_id` BIGINT NOT NULL, + `category_id` BIGINT NOT NULL, + `title` VARCHAR(30) NOT NULL, + `location` VARCHAR(255) NOT NULL, + `price` INT NOT NULL, + `capacity` INT NOT NULL, + `image` VARCHAR(255) NOT NULL, + `description` VARCHAR(255) NOT NULL, + `materials` VARCHAR(255) NULL, + `applicant_sum` INT NOT NULL DEFAULT 0, + `is_deleted` BOOLEAN NOT NULL DEFAULT FALSE, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`lesson_id`), + FOREIGN KEY (`host_id`) REFERENCES `host` (`host_id`), + FOREIGN KEY (`category_id`) REFERENCES `category` (`category_id`) +); + +CREATE TABLE `revenue` ( + `revenue_id` BIGINT NOT NULL AUTO_INCREMENT, + `lesson_id` BIGINT NOT NULL, + `revenue` BIGINT NOT NULL, + `material_price` INT NOT NULL DEFAULT 0, + `rental_price` INT NOT NULL DEFAULT 0, + `etc_price` INT NOT NULL DEFAULT 0, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`revenue_id`), + FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`lesson_id`) +); + +CREATE TABLE `lessondate` ( + `lessondate_id` BIGINT NOT NULL AUTO_INCREMENT, + `lesson_id` BIGINT NOT NULL, + `date` DATE NOT NULL, + `start_time` DATETIME NOT NULL, + `end_time` DATETIME NOT NULL, + `applicant` INT NOT NULL DEFAULT 0, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`lessondate_id`), + FOREIGN KEY (`lesson_id`) REFERENCES `lesson` (`lesson_id`) +); + +CREATE TABLE `reservation` ( + `reservation_id` BIGINT NOT NULL AUTO_INCREMENT, + `user_id` BIGINT NOT NULL COMMENT '게스트', + `lessondate_id` BIGINT NOT NULL, + `applicant` INT NOT NULL DEFAULT 0, + `is_deleted` BOOLEAN NOT NULL DEFAULT FALSE, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`reservation_id`), + FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`), + FOREIGN KEY (`lessondate_id`) REFERENCES `lessondate` (`lessondate_id`) +); + +CREATE TABLE `transaction` ( + `transaction_id` BIGINT NOT NULL AUTO_INCREMENT, + `deposit_id` BIGINT NOT NULL COMMENT '호스트', + `withdraw_id` BIGINT NOT NULL COMMENT '게스트', + `reservation_id` BIGINT NOT NULL, + `payment` INT NOT NULL COMMENT '포인트 합산 금액', + `point` INT NOT NULL, + `type` ENUM('QR', 'PENDING', 'COMPLETE') NOT NULL, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`transaction_id`), + FOREIGN KEY (`deposit_id`) REFERENCES `account` (`account_id`), + FOREIGN KEY (`withdraw_id`) REFERENCES `account` (`account_id`), + FOREIGN KEY (`reservation_id`) REFERENCES `reservation` (`reservation_id`) +); + +CREATE TABLE `hanastorage` ( + `hanastorage_id` BIGINT NOT NULL AUTO_INCREMENT, + `transaction_id` BIGINT NOT NULL, + `lessondate` DATE NOT NULL, + `is_deleted` BOOLEAN NOT NULL DEFAULT FALSE, + `created_date` DATETIME NOT NULL DEFAULT NOW(), + `updated_date` DATETIME NULL, + PRIMARY KEY (`hanastorage_id`), + FOREIGN KEY (`transaction_id`) REFERENCES `transaction` (`transaction_id`) +); \ No newline at end of file