Skip to content

Commit

Permalink
Merge pull request #45 from woowacourse-teams/feature/#16
Browse files Browse the repository at this point in the history
  • Loading branch information
kunsanglee authored Jul 21, 2024
2 parents 2b5345d + e489d82 commit ad8f78d
Show file tree
Hide file tree
Showing 19 changed files with 446 additions and 18 deletions.
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package server.haengdong.application;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import server.haengdong.application.request.BillActionAppRequest;
import server.haengdong.domain.Action;
import server.haengdong.domain.BillAction;
import server.haengdong.domain.Event;
import server.haengdong.persistence.ActionRepository;
import server.haengdong.persistence.BillActionRepository;
import server.haengdong.persistence.EventRepository;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class BillActionService {

private final BillActionRepository billActionRepository;
private final ActionRepository actionRepository;
private final EventRepository eventRepository;

@Transactional
public void saveAllBillAction(String eventToken, List<BillActionAppRequest> requests) {
Event event = eventRepository.findByToken(eventToken)
.orElseThrow(() -> new IllegalArgumentException("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 이벀트 ν† ν°μž…λ‹ˆλ‹€."));
Action action = createStartAction(event);

for (BillActionAppRequest request : requests) {
BillAction billAction = request.toBillAction(action);
billActionRepository.save(billAction);
action = action.next();
}
}

private Action createStartAction(Event event) {
return actionRepository.findLastByEvent(event)
.map(Action::next)
.orElse(Action.createFirst(event));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public EventAppResponse saveEvent(EventAppRequest request) {
Event event = request.toEvent(token);
eventRepository.save(event);

return EventAppResponse.of(event);
return EventAppResponse.of(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package server.haengdong.application.request;

import server.haengdong.domain.Action;
import server.haengdong.domain.BillAction;

public record BillActionAppRequest(
String title,
Long price
) {

public BillAction toBillAction(Action action) {
return new BillAction(action, title, price);
}
}
15 changes: 15 additions & 0 deletions server/src/main/java/server/haengdong/domain/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
@Entity
public class Action {

private static final long FIRST_SEQUENCE = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -23,4 +25,17 @@ public class Action {
private Event event;

private Long sequence;

public Action(Event event, Long sequence) {
this.event = event;
this.sequence = sequence;
}

public static Action createFirst(Event event) {
return new Action(event, FIRST_SEQUENCE);
}

public Action next() {
return new Action(event, sequence + 1);
}
}
38 changes: 35 additions & 3 deletions server/src/main/java/server/haengdong/domain/BillAction.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package server.haengdong.domain;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import java.math.BigDecimal;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -16,14 +17,45 @@
@Entity
public class BillAction {

private static final int MIN_TITLE_LENGTH = 2;
private static final int MAX_TITLE_LENGTH = 30;
private static final long MIN_PRICE = 1L;
private static final long MAX_PRICE = 10_000_000L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private Action action;

@Column(length = MAX_TITLE_LENGTH)
private String title;

private BigDecimal price;
private Long price;

public BillAction(Action action, String title, Long price) {
validateTitle(title);
validatePrice(price);
this.action = action;
this.title = title.trim();
this.price = price;
}

private void validateTitle(String title) {
int titleLength = title.trim().length();
if (titleLength < MIN_TITLE_LENGTH || titleLength > MAX_TITLE_LENGTH) {
throw new IllegalArgumentException("μ•žλ’€ 곡백을 μ œκ±°ν•œ μ§€μΆœ λ‚΄μ—­ 제λͺ©μ€ 2 ~ 30μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.");
}
}

private void validatePrice(Long price) {
if (price < MIN_PRICE || price > MAX_PRICE) {
throw new IllegalArgumentException("μ§€μΆœ κΈˆμ•‘μ€ 10,000,000 μ΄ν•˜μ˜ μžμ—°μˆ˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.");
}
}

public Long getSequence() {
return action.getSequence();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package server.haengdong.persistence;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import server.haengdong.domain.Action;
import server.haengdong.domain.Event;

@Repository
public interface ActionRepository extends JpaRepository<Action, Long> {

@Query("""
SELECT a
FROM Action a
WHERE a.event = :event
ORDER BY a.sequence DESC
LIMIT 1
""")
Optional<Action> findLastByEvent(Event event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package server.haengdong.persistence;

import java.util.List;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import server.haengdong.domain.BillAction;
import server.haengdong.domain.Event;

@Repository
public interface BillActionRepository extends JpaRepository<BillAction, Long> {

@EntityGraph(attributePaths = {"action"})
List<BillAction> findByAction_Event(Event event);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package server.haengdong.persistence;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import server.haengdong.domain.Event;

@Repository
public interface EventRepository extends JpaRepository<Event, Long> {

Optional<Event> findByToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package server.haengdong.presentation;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import server.haengdong.application.BillActionService;
import server.haengdong.presentation.request.BillActionsSaveRequest;

@RequiredArgsConstructor
@RestController
public class BillActionController {

private final BillActionService billActionService;

@PostMapping("/api/events/{token}/actions/bills")
public ResponseEntity<Void> saveAllBillAction(
@PathVariable String token,
@RequestBody @Valid BillActionsSaveRequest request
) {
billActionService.saveAllBillAction(token, request.toAppRequests());

return ResponseEntity.ok()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package server.haengdong.presentation.request;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import server.haengdong.application.request.BillActionAppRequest;

public record BillActionSaveRequest(

@NotNull
@Size(min = 2, max = 30)
String title,

@NotNull
@Positive
Long price
) {

public BillActionAppRequest toAppRequest() {
return new BillActionAppRequest(title, price);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package server.haengdong.presentation.request;

import jakarta.validation.Valid;
import java.util.List;
import server.haengdong.application.request.BillActionAppRequest;

public record BillActionsSaveRequest(@Valid List<BillActionSaveRequest> actions) {

public List<BillActionAppRequest> toAppRequests() {
return actions.stream()
.map(BillActionSaveRequest::toAppRequest)
.toList();
}
}
1 change: 0 additions & 1 deletion server/src/main/resources/application.properties

This file was deleted.

15 changes: 15 additions & 0 deletions server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
spring:
h2:
console:
enabled: true
path: /h2-console
datasource:
url: jdbc:h2:mem:database
jpa:
defer-datasource-initialization: true
show-sql: true
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: create-drop
13 changes: 0 additions & 13 deletions server/src/test/java/server/haengdong/ServerApplicationTests.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package server.haengdong.application;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;

import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import server.haengdong.application.request.BillActionAppRequest;
import server.haengdong.domain.BillAction;
import server.haengdong.domain.Event;
import server.haengdong.persistence.BillActionRepository;
import server.haengdong.persistence.EventRepository;

@SpringBootTest
class BillActionServiceTest {

@Autowired
private BillActionService billActionService;

@Autowired
private EventRepository eventRepository;

@Autowired
private BillActionRepository billActionRepository;

@DisplayName("μ§€μΆœ 내역을 μƒμ„±ν•œλ‹€.")
@Test
void saveAllBillAction() {
String token = "TOKEN";
Event event = new Event("감자", token);
Event savedEvent = eventRepository.save(event);

List<BillActionAppRequest> requests = List.of(
new BillActionAppRequest("뽕쑱", 10_000L),
new BillActionAppRequest("인생λ§₯μ£Ό", 15_000L)
);

billActionService.saveAllBillAction(token, requests);

List<BillAction> actions = billActionRepository.findByAction_Event(savedEvent);

assertThat(actions).extracting(BillAction::getTitle, BillAction::getPrice, BillAction::getSequence)
.containsExactlyInAnyOrder(
tuple("뽕쑱", 10_000L, 1L),
tuple("인생λ§₯μ£Ό", 15_000L, 2L)
);
}

@DisplayName("μ΄λ²€νŠΈκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©΄ μ§€μΆœ 내역을 생성할 수 μ—†λ‹€.")
@Test
void saveAllBillAction1() {
List<BillActionAppRequest> requests = List.of(
new BillActionAppRequest("뽕쑱", 10_000L),
new BillActionAppRequest("인생λ§₯μ£Ό", 15_000L)
);

assertThatThrownBy(() -> billActionService.saveAllBillAction("token", requests))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 이벀트 ν† ν°μž…λ‹ˆλ‹€.");
}
}
Loading

0 comments on commit ad8f78d

Please sign in to comment.