Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix : flyway 언더바 추가 #717

Merged
merged 3 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import in.koreatech.koin.domain.dining.dto.DiningLikeRequest;
import in.koreatech.koin.domain.dining.dto.DiningResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -32,4 +35,16 @@ ResponseEntity<List<DiningResponse>> getDinings(
@DateTimeFormat(pattern = "yyMMdd")
@Parameter(description = "조회 날짜(yyMMdd)") @RequestParam(required = false) LocalDate date
);

@Operation(summary = "식단 좋아요")
@PatchMapping("/dining/like")
ResponseEntity<Void> likeDining(
@RequestBody DiningLikeRequest diningLikeRequest
);

@Operation(summary = "식단 좋아요 취소")
@PatchMapping("/dining/like/cancel")
ResponseEntity<Void> likeDiningCancel(
@RequestBody DiningLikeRequest diningLikeRequest
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import in.koreatech.koin.domain.dining.dto.DiningLikeRequest;
import in.koreatech.koin.domain.dining.dto.DiningResponse;
import in.koreatech.koin.domain.dining.service.DiningService;
import lombok.RequiredArgsConstructor;
Expand All @@ -27,4 +30,20 @@ public ResponseEntity<List<DiningResponse>> getDinings(
List<DiningResponse> responses = diningService.getDinings(date);
return ResponseEntity.ok(responses);
}

@PatchMapping("/dining/like")
public ResponseEntity<Void> likeDining(
@RequestBody DiningLikeRequest diningLikeRequest
) {
diningService.likeDining(diningLikeRequest);
return ResponseEntity.ok().build();
}

@PatchMapping("/dining/like/cancel")
public ResponseEntity<Void> likeDiningCancel(
@RequestBody DiningLikeRequest diningLikeRequest
) {
diningService.likeDiningCancel(diningLikeRequest);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.domain.dining.dto;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record DiningLikeRequest(
@Schema(description = "메뉴 고유 ID", example = "1", requiredMode = REQUIRED)
Integer diningId,

@Schema(description = "사용자 ID", example = "1", requiredMode = REQUIRED)
Integer userId
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ public record DiningResponse(

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "메뉴 변경 시각", example = "2024-04-04 23:01:52", requiredMode = NOT_REQUIRED)
LocalDateTime changedAt
LocalDateTime changedAt,

@Schema(description = "식단 좋아요 수", example = "1", requiredMode = REQUIRED)
Integer likes
) {

public static DiningResponse from(Dining dining) {
Expand All @@ -78,7 +81,8 @@ public static DiningResponse from(Dining dining) {
dining.getCreatedAt(),
dining.getUpdatedAt(),
dining.getSoldOut(),
dining.getIsChanged()
dining.getIsChanged(),
dining.getLikes()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.domain.dining.exception;

import in.koreatech.koin.global.exception.DuplicationException;

public class DuplicateLikeException extends DuplicationException {
private static final String DEFAULT_MESSAGE = "이미 좋아요를 누른 식단입니다!";

public DuplicateLikeException(String message) {
super(message);
}

public DuplicateLikeException(String message, String detail) {
super(message, detail);
}

public static DuplicateLikeException withDetail(Integer diningId, Integer userId) {
return new DuplicateLikeException(DEFAULT_MESSAGE, "diningId: '" + diningId + "'" + "userId: " + userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package in.koreatech.koin.domain.dining.exception;

import in.koreatech.koin.global.exception.DataNotFoundException;

public class LikeNotFoundException extends DataNotFoundException {
private static final String DEFAULT_MESSAGE = "좋아요를 누른적이 없는 식단입니다!";

public LikeNotFoundException(String message) {
super(message);
}

public LikeNotFoundException(String message, String detail) {
super(message, detail);
}

public static LikeNotFoundException withDetail(Integer diningId, Integer userId) {
return new LikeNotFoundException(DEFAULT_MESSAGE, "diningId: '" + diningId + "'" + "userId: " + userId);
}
}
15 changes: 14 additions & 1 deletion src/main/java/in/koreatech/koin/domain/dining/model/Dining.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class Dining extends BaseEntity {
@Column(name = "is_changed", columnDefinition = "DATETIME")
private LocalDateTime isChanged;

@Column(name = "likes")
private Integer likes = 0;

@Builder
private Dining(
LocalDate date,
Expand All @@ -86,7 +89,8 @@ private Dining(
String menu,
String imageUrl,
LocalDateTime soldOut,
LocalDateTime isChanged
LocalDateTime isChanged,
Integer likes
) {
this.date = date;
this.type = type;
Expand All @@ -98,6 +102,7 @@ private Dining(
this.imageUrl = imageUrl;
this.soldOut = soldOut;
this.isChanged = isChanged;
this.likes = likes;
}

public void setImageUrl(String imageUrl) {
Expand All @@ -112,6 +117,14 @@ public void cancelSoldOut() {
this.soldOut = null;
}

public void likesDining() {
this.likes++;
}

public void likesDiningCancel() {
this.likes--;
}

/**
* DB에 "[메뉴, 메뉴, ...]" 형태로 저장되어 List로 파싱하여 반환
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package in.koreatech.koin.domain.dining.model;

import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name = "dining_likes")
@NoArgsConstructor(access = PROTECTED)
public class DiningLikes {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;

@Column(name = "dining_id", nullable = false)
private Integer diningId;

@Column(name = "user_id", nullable = false)
private Integer userId;

@Builder
private DiningLikes(
Integer diningId,
Integer userId
) {
this.diningId = diningId;
this.userId = userId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package in.koreatech.koin.domain.dining.repository;

import org.springframework.data.repository.Repository;

import in.koreatech.koin.domain.dining.model.DiningLikes;

public interface DiningLikesRepository extends Repository<DiningLikes, Integer> {

Boolean existsByDiningIdAndUserId(Integer diningId, Integer userId);

DiningLikes save(DiningLikes diningLikes);

void deleteByDiningIdAndUserId(Integer diningId, Integer userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import in.koreatech.koin.domain.dining.dto.DiningLikeRequest;
import in.koreatech.koin.domain.dining.dto.DiningResponse;
import in.koreatech.koin.domain.dining.exception.DuplicateLikeException;
import in.koreatech.koin.domain.dining.exception.LikeNotFoundException;
import in.koreatech.koin.domain.dining.model.Dining;
import in.koreatech.koin.domain.dining.model.DiningLikes;
import in.koreatech.koin.domain.dining.repository.DiningLikesRepository;
import in.koreatech.koin.domain.dining.repository.DiningRepository;
import lombok.RequiredArgsConstructor;

Expand All @@ -17,6 +23,7 @@
public class DiningService {

private final DiningRepository diningRepository;
private final DiningLikesRepository diningLikesRepository;

private final Clock clock;

Expand All @@ -29,4 +36,37 @@ public List<DiningResponse> getDinings(LocalDate date) {
.map(DiningResponse::from)
.toList();
}

@Transactional(readOnly = false)
public void likeDining(DiningLikeRequest diningLikeRequest) {
if (diningLikesRepository.existsByDiningIdAndUserId(diningLikeRequest.diningId(), diningLikeRequest.userId())) {
throw DuplicateLikeException.withDetail(diningLikeRequest.diningId(), diningLikeRequest.userId());
}

Dining dining = diningRepository.getById(diningLikeRequest.diningId());
dining.likesDining();
diningRepository.save(dining);

diningLikesRepository.save(DiningLikes.builder()
.diningId(diningLikeRequest.diningId())
.userId(diningLikeRequest.userId())
.build());
}

@Transactional(readOnly = false)
public void likeDiningCancel(DiningLikeRequest diningLikeRequest) {
if (!diningLikesRepository.existsByDiningIdAndUserId(diningLikeRequest.diningId(),
diningLikeRequest.userId())) {
throw LikeNotFoundException.withDetail(diningLikeRequest.diningId(), diningLikeRequest.userId());
}

Dining dining = diningRepository.getById(diningLikeRequest.diningId());
dining.likesDiningCancel();
diningRepository.save(dining);

diningLikesRepository.deleteByDiningIdAndUserId(
diningLikeRequest.diningId(),
diningLikeRequest.userId()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS dining_likes (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
dining_id INT NOT NULL,
user_id INT NOT NULL
);

ALTER TABLE `dining_menus` ADD COLUMN `likes` INT DEFAULT 0;
59 changes: 57 additions & 2 deletions src/test/java/in/koreatech/koin/acceptance/DiningApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ void findDinings() {
"created_at": "2024-01-15 12:00:00",
"updated_at": "2024-01-15 12:00:00",
"soldout_at": null,
"changed_at": null
"changed_at": null,
"likes": 0
}
]
""");
Expand Down Expand Up @@ -135,7 +136,8 @@ void nullDate() {
"created_at": "2024-01-15 12:00:00",
"updated_at": "2024-01-15 12:00:00",
"soldout_at": null,
"changed_at": null
"changed_at": null,
"likes": 0
}
]
""");
Expand Down Expand Up @@ -292,4 +294,57 @@ void checkSoldOutNotificationResend() {

verify(coopEventListener, never()).onDiningSoldOutRequest(any());
}

@Test
@DisplayName("특정 식단의 좋아요를 누른다")
void likeDining() {
RestAssured.given()
.contentType(ContentType.JSON)
.body(String.format("""
{
"dining_id": "%s",
"user_id": %s
}
""", A코너_점심.getId(), coop_준기.getId())
)
.when()
.patch("/dining/like")
.then()
.statusCode(HttpStatus.OK.value())
.extract();
}

@Test
@DisplayName("특정 식단의 좋아요 중복해서 누르면 에러")
void likeDiningDuplicate() {
RestAssured.given()
.contentType(ContentType.JSON)
.body(String.format("""
{
"dining_id": "%s",
"user_id": %s
}
""", A코너_점심.getId(), coop_준기.getId())
)
.when()
.patch("/dining/like")
.then()
.statusCode(HttpStatus.OK.value())
.extract();

RestAssured.given()
.contentType(ContentType.JSON)
.body(String.format("""
{
"dining_id": "%s",
"user_id": %s
}
""", A코너_점심.getId(), coop_준기.getId())
)
.when()
.patch("/dining/like")
.then()
.statusCode(HttpStatus.CONFLICT.value())
.extract();
}
}
Loading
Loading