Skip to content
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
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ dependencies {
annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:7.0:jpa"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'

// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.UMC.domain.mission.controller;

import com.example.UMC.domain.mission.dto.response.MissionChallengeResponse;
import com.example.UMC.domain.mission.service.MissionService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/my/missions")
public class MissionController {

private final MissionService missionService;

/**
* [POST] /api/my/missions/{missionId}/challenge
* 미션 도전하기
*/
@PostMapping("/{missionId}/challenge")
public ResponseEntity<MissionChallengeResponse> challengeMission(
@RequestHeader("X-USER-ID") Long userId,
@PathVariable Long missionId
) {
MissionChallengeResponse response = missionService.challengeMission(userId, missionId);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.UMC.domain.mission.dto.response;

import com.example.UMC.domain.enums.entity.MissionStatus;
import lombok.Builder;

@Builder
public record MissionChallengeResponse(
Long userMissionId,
Long missionId,
Long userId,
MissionStatus status
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.UMC.domain.mission.exception;

import com.example.UMC.global.apiPayload.code.BaseErrorCode;
import com.example.UMC.global.apiPayload.exception.GeneralException;

public class MissionException extends GeneralException {
public MissionException(BaseErrorCode code) {
super(code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.UMC.domain.mission.exception.code;

import com.example.UMC.global.apiPayload.code.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum MissionErrorCode implements BaseErrorCode {

MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION4004", "해당 미션을 찾을 수 없습니다."),
MISSION_ALREADY_CHALLENGING(HttpStatus.CONFLICT, "MISSION4090", "이미 도전 중인 미션입니다."),
MISSION_ALREADY_COMPLETED(HttpStatus.CONFLICT, "MISSION4091", "이미 완료한 미션입니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.example.UMC.domain.mission.service;

import com.example.UMC.domain.enums.entity.MissionStatus;
import com.example.UMC.domain.mission.dto.response.MissionChallengeResponse;
import com.example.UMC.domain.mission.entity.Mission;
import com.example.UMC.domain.mission.entity.UserMission;
import com.example.UMC.domain.mission.exception.MissionException;
import com.example.UMC.domain.mission.exception.code.MissionErrorCode;
import com.example.UMC.domain.mission.repository.MissionRepository;
import com.example.UMC.domain.mission.repository.UserMissionRepository;
import com.example.UMC.domain.user.entity.User;
import com.example.UMC.domain.user.exception.UserException;
import com.example.UMC.domain.user.exception.code.UserErrorCode;
import com.example.UMC.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
public class MissionService {
private final MissionRepository missionRepository;
private final UserRepository userRepository;
private final UserMissionRepository userMissionRepository;

/**
* 미션 도전하기 API
*/
@Transactional
public MissionChallengeResponse challengeMission(Long userId, Long missionId) {

//유저 확인
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND));

//미션 확인
Mission mission = missionRepository.findById(missionId)
.orElseThrow(() -> new MissionException(MissionErrorCode.MISSION_NOT_FOUND));

//유저가 이미 같은 미션을 도전 중이거나 완료했는지 확인
var existing = userMissionRepository.findAllByUser(user, null)
.stream()
.filter(um -> um.getMission().getId().equals(missionId))
.findFirst();

if (existing.isPresent()) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MissionStatus에 MissionErrorCode 넣어서 사용하면 조건문 처리 안 해도 되용

var current = existing.get();
if (current.getStatus() == MissionStatus.PROCESS)
throw new MissionException(MissionErrorCode.MISSION_ALREADY_CHALLENGING);
if (current.getStatus() == MissionStatus.COMPLETE)
throw new MissionException(MissionErrorCode.MISSION_ALREADY_COMPLETED);
}

//새로운 UserMisson 생성
UserMission newChanllenge=UserMission.builder()
.user(user)
.mission(mission)
.region(mission.getRegion())
.status(MissionStatus.PROCESS)
.challengeAt(LocalDateTime.now())
.build();

UserMission saved = userMissionRepository.save(newChanllenge);

//응답 반환
return MissionChallengeResponse.builder()
.userMissionId(saved.getId())
.missionId(mission.getId())
.userId(user.getId())
.status(saved.getStatus())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
package com.example.UMC.domain.review.controller;

import com.example.UMC.domain.review.dto.request.ReviewCreateRequest;
import com.example.UMC.domain.review.dto.response.ReviewResponse;
import com.example.UMC.domain.review.entity.Review;
import com.example.UMC.domain.review.repository.ReviewQueryRepository;
import com.example.UMC.domain.review.service.ReviewService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/reviews")
@RequestMapping("/api/stores")
public class ReviewController {

private final ReviewQueryRepository reviewQueryRepository;
private final ReviewService reviewService;

/**
* [POST] /api/stores/{storeId}/reviews
* 가게에 리뷰 작성 API
*/
@PostMapping("/{storeId}/reviews")
public ResponseEntity<ReviewResponse> createReview(
@RequestHeader("X-USER-ID") Long userId,
@PathVariable Long storeId,
@RequestBody @Valid ReviewCreateRequest request) {
ReviewResponse response = reviewService.createReview(userId, storeId, request);
return ResponseEntity.ok(response);
}

// 태스트 컨틀로러

Expand All @@ -30,6 +49,7 @@ public Page<Review> getReviews(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {

Pageable pageable = PageRequest.of(page, size);
return reviewQueryRepository.findReviews(storeId, storeName, regionId, star, pageable);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.UMC.domain.review.dto.request;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record ReviewCreateRequest(
@Min(1) @Max(5)
Integer rating,

@NotBlank
@Size(max = 1000)
String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.UMC.domain.review.dto.response;

public record ReviewResponse(
Long reviewId,
Long storeId,
Long userId,
Integer rating,
String content
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.UMC.domain.review.exception;

import com.example.UMC.global.apiPayload.code.BaseErrorCode;
import com.example.UMC.global.apiPayload.exception.GeneralException;

public class ReviewException extends GeneralException {
public ReviewException(BaseErrorCode code) {
super(code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.UMC.domain.review.exception.code;

import com.example.UMC.global.apiPayload.code.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ReviewErrorCode implements BaseErrorCode {
REVIEW_NOT_FOUND(HttpStatus.NOT_FOUND, "REVIEW4004", "해당 리뷰를 찾을 수 없습니다."),
DUPLICATE_REVIEW(HttpStatus.CONFLICT, "REVIEW4090", "이미 리뷰를 작성했습니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example.UMC.domain.review.service;

import com.example.UMC.domain.review.dto.request.ReviewCreateRequest;
import com.example.UMC.domain.review.dto.response.ReviewResponse;
import com.example.UMC.domain.review.entity.Review;
import com.example.UMC.domain.review.repository.ReviewRepository;
import com.example.UMC.domain.store.entity.Store;
import com.example.UMC.domain.store.exception.StoreException;
import com.example.UMC.domain.store.exception.code.StoreErrorCode;
import com.example.UMC.domain.store.repository.StoreRepository;
import com.example.UMC.domain.user.entity.User;
import com.example.UMC.domain.user.exception.UserException;
import com.example.UMC.domain.user.exception.code.UserErrorCode;
import com.example.UMC.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ReviewService {
private final ReviewRepository reviewRepository;
private final UserRepository userRepository;
private final StoreRepository storeRepository;

@Transactional
public ReviewResponse createReview(Long userId, Long storeId, ReviewCreateRequest request) {

//유저 존재 확인
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND));

//가게 검증
Store store = storeRepository.findById(storeId)
.orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND));

//리뷰 생성
Review review = Review.builder()
.store(store)
.user(user)
.region(store.getRegion())
.rating(request.rating())
.content(request.content())
.build();

Review saved = reviewRepository.save(review);

//응답 DTO 변환
return new ReviewResponse(
saved.getId(),
store.getId(),
user.getId(),
saved.getRating(),
saved.getContent()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.UMC.domain.store.exception;

import com.example.UMC.global.apiPayload.code.BaseErrorCode;
import com.example.UMC.global.apiPayload.exception.GeneralException;

public class StoreException extends GeneralException {
public StoreException(BaseErrorCode code) {
super(code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.UMC.domain.store.exception.code;

import com.example.UMC.global.apiPayload.code.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum StoreErrorCode implements BaseErrorCode {
STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE4004", "해당 가게를 찾을 수 없습니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.UMC.domain.store.repository;

import com.example.UMC.domain.store.entity.Store;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StoreRepository extends JpaRepository<Store, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@Getter
@AllArgsConstructor
public enum UserErrorCode implements BaseErrorCode {
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER4001", "해당 사용자를 찾을 수 없습니다."),
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER4004", "해당 사용자를 찾을 수 없습니다."),
DUPLICATE_EMAIL(HttpStatus.BAD_REQUEST, "USER4002","이미 존재하는 이메일입니다.");

private final HttpStatus status;
Expand Down
Loading