diff --git a/build.gradle b/build.gradle index e98312f..b0dfdf1 100644 --- a/build.gradle +++ b/build.gradle @@ -27,12 +27,20 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // 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' + // QueryDSL : OpenFeign implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0" implementation "io.github.openfeign.querydsl:querydsl-core:7.0" diff --git a/src/main/java/com/umc_study/mission_server/common/GeneralExceptionAdvice.java b/src/main/java/com/umc_study/mission_server/common/GeneralExceptionAdvice.java index 739cb1e..9aafaa7 100644 --- a/src/main/java/com/umc_study/mission_server/common/GeneralExceptionAdvice.java +++ b/src/main/java/com/umc_study/mission_server/common/GeneralExceptionAdvice.java @@ -4,7 +4,10 @@ import com.umc_study.mission_server.common.response.BaseResponseCode; import com.umc_study.mission_server.common.response.GeneralErrorCode; import com.umc_study.mission_server.common.response.GeneralException; +import java.util.HashMap; +import java.util.Map; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -24,6 +27,23 @@ public ResponseEntity> handleException( ); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity>> handleException( + MethodArgumentNotValidException ex + ) { + // 검사에 실패한 필드와 그에 대한 메시지를 저장하는 Map + Map errors = new HashMap<>(); + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage()) + ); + + GeneralErrorCode code = GeneralErrorCode.BAD_REQUEST; + ApiResponse> errorResponse = ApiResponse.error(code, errors); + + // 에러 코드, 메시지와 함께 errors를 반환 + return ResponseEntity.status(code.getStatus()).body(errorResponse); + } + // 그 외의 정의되지 않은 모든 예외 처리 @ExceptionHandler(Exception.class) public ResponseEntity> handleException( diff --git a/src/main/java/com/umc_study/mission_server/common/annotation/ExistsFoodType.java b/src/main/java/com/umc_study/mission_server/common/annotation/ExistsFoodType.java new file mode 100644 index 0000000..ed40bb2 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/common/annotation/ExistsFoodType.java @@ -0,0 +1,20 @@ +package com.umc_study.mission_server.common.annotation; + +import com.umc_study.mission_server.common.validator.FoodTypeExistenceValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = FoodTypeExistenceValidator.class) +@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistsFoodType { + String message() default "해당 음식이 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/umc_study/mission_server/common/config/SwaggerConfig.java b/src/main/java/com/umc_study/mission_server/common/config/SwaggerConfig.java new file mode 100644 index 0000000..453f4e8 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/common/config/SwaggerConfig.java @@ -0,0 +1,35 @@ +package com.umc_study.mission_server.common.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI swagger() { + Info info = new Info().title("Project").description("Project Swagger").version("0.0.1"); + + // JWT 토큰 헤더 방식 + String securityScheme = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme); + + Components components = new Components() + .addSecuritySchemes(securityScheme, new SecurityScheme() + .name(securityScheme) + .type(SecurityScheme.Type.HTTP) + .scheme("Bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .info(info) + .addServersItem(new Server().url("/")) + .addSecurityItem(securityRequirement) + .components(components); + } +} diff --git a/src/main/java/com/umc_study/mission_server/common/validator/FoodTypeExistenceValidator.java b/src/main/java/com/umc_study/mission_server/common/validator/FoodTypeExistenceValidator.java new file mode 100644 index 0000000..b2db6a6 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/common/validator/FoodTypeExistenceValidator.java @@ -0,0 +1,30 @@ +package com.umc_study.mission_server.common.validator; + +import com.umc_study.mission_server.common.annotation.ExistsFoodType; +import com.umc_study.mission_server.store.exception.StoreErrorCode; +import com.umc_study.mission_server.store.repository.FoodTypeRepository; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class FoodTypeExistenceValidator implements ConstraintValidator> { + private final FoodTypeRepository foodTypeRepository; + + @Override + public boolean isValid(List values, ConstraintValidatorContext context) { + boolean isValid = values.stream() + .allMatch(foodTypeRepository::existsById); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(StoreErrorCode.FOOD_TYPE_NOT_FOUND.getMessage()) + .addConstraintViolation(); + } + + return isValid; + } +} \ No newline at end of file diff --git a/src/main/java/com/umc_study/mission_server/member/controller/MemberController.java b/src/main/java/com/umc_study/mission_server/member/controller/MemberController.java new file mode 100644 index 0000000..10f0abe --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/controller/MemberController.java @@ -0,0 +1,33 @@ +package com.umc_study.mission_server.member.controller; + +import com.umc_study.mission_server.common.response.ApiResponse; +import com.umc_study.mission_server.member.dto.MemberResponse; +import com.umc_study.mission_server.member.dto.SignupRequest; +import com.umc_study.mission_server.member.dto.UpdatePreferFoodTypesRequest; +import com.umc_study.mission_server.member.service.MemberService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/members") +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + @PostMapping("/signup") + public ApiResponse signup(@RequestBody SignupRequest request) { + return ApiResponse.ok(memberService.signup(request)); + } + + @PutMapping("/prefer-foods") + public ApiResponse updatePreferFoods(@RequestBody @Valid UpdatePreferFoodTypesRequest request) { + memberService.updatePreferFoodTypes(request.memberId(), request.foodTypes()); + return ApiResponse.ok(null); + } +} diff --git a/src/main/java/com/umc_study/mission_server/member/dto/MemberResponse.java b/src/main/java/com/umc_study/mission_server/member/dto/MemberResponse.java new file mode 100644 index 0000000..6262c21 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/dto/MemberResponse.java @@ -0,0 +1,38 @@ +package com.umc_study.mission_server.member.dto; + +import com.umc_study.mission_server.member.entity.Member; +import com.umc_study.mission_server.member.entity.MemberGender; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Builder; + +@Builder +public record MemberResponse( + Long id, + String name, + MemberGender sex, + LocalDate birthday, + String address1, + String address2, + LocalDateTime registeredAt, + String nickname, + String email, + String phoneNumber, + Long currentPoint +) { + public static MemberResponse from(Member member) { + return MemberResponse.builder() + .id(member.getId()) + .name(member.getName()) + .sex(member.getSex()) + .birthday(member.getBirthday()) + .address1(member.getAddress1()) + .address2(member.getAddress2()) + .registeredAt(member.getRegisteredAt()) + .nickname(member.getNickname()) + .email(member.getEmail()) + .phoneNumber(member.getPhoneNumber()) + .currentPoint(member.getCurrentPoint()) + .build(); + } +} diff --git a/src/main/java/com/umc_study/mission_server/member/dto/NotificationSettingsDto.java b/src/main/java/com/umc_study/mission_server/member/dto/NotificationSettingsDto.java new file mode 100644 index 0000000..51c43cf --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/dto/NotificationSettingsDto.java @@ -0,0 +1,8 @@ +package com.umc_study.mission_server.member.dto; + +public record NotificationSettingsDto( + boolean newEvent, + boolean reviewReply, + boolean askReply +) { +} diff --git a/src/main/java/com/umc_study/mission_server/member/dto/SignupAgreements.java b/src/main/java/com/umc_study/mission_server/member/dto/SignupAgreements.java new file mode 100644 index 0000000..7d12937 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/dto/SignupAgreements.java @@ -0,0 +1,9 @@ +package com.umc_study.mission_server.member.dto; + +public record SignupAgreements( + boolean tos, + boolean privacy, + boolean gps, + boolean marketing +) { +} diff --git a/src/main/java/com/umc_study/mission_server/member/dto/SignupRequest.java b/src/main/java/com/umc_study/mission_server/member/dto/SignupRequest.java new file mode 100644 index 0000000..ef996e7 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/dto/SignupRequest.java @@ -0,0 +1,28 @@ +package com.umc_study.mission_server.member.dto; + +import com.umc_study.mission_server.member.entity.Member; +import com.umc_study.mission_server.member.entity.MemberGender; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; + +public record SignupRequest( + @NotBlank + String name, + @NotBlank + String nickname, + @NotNull + SignupAgreements agreements, + @NotBlank + MemberGender sex, + @NotNull + LocalDate birthday, + @NotBlank + String address1, + String address2, + String email, + String phoneNumber, + @NotNull + NotificationSettingsDto notificationSettings +) { +} diff --git a/src/main/java/com/umc_study/mission_server/member/dto/UpdatePreferFoodTypesRequest.java b/src/main/java/com/umc_study/mission_server/member/dto/UpdatePreferFoodTypesRequest.java new file mode 100644 index 0000000..6d245aa --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/dto/UpdatePreferFoodTypesRequest.java @@ -0,0 +1,11 @@ +package com.umc_study.mission_server.member.dto; + +import com.umc_study.mission_server.common.annotation.ExistsFoodType; +import java.util.List; + +public record UpdatePreferFoodTypesRequest( + Long memberId, + @ExistsFoodType + List foodTypes +) { +} diff --git a/src/main/java/com/umc_study/mission_server/member/entity/Member.java b/src/main/java/com/umc_study/mission_server/member/entity/Member.java index 3bcd3c4..65a6c69 100644 --- a/src/main/java/com/umc_study/mission_server/member/entity/Member.java +++ b/src/main/java/com/umc_study/mission_server/member/entity/Member.java @@ -85,7 +85,7 @@ public class Member extends BaseEntity { private Boolean notificationAskReply; @Column(name = "current_point", nullable = true) - private Long currentPoint; + private Long currentPoint = 0L; @OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true) @Builder.Default diff --git a/src/main/java/com/umc_study/mission_server/member/entity/MemberFoodType.java b/src/main/java/com/umc_study/mission_server/member/entity/MemberFoodType.java index 27f003f..3678c70 100644 --- a/src/main/java/com/umc_study/mission_server/member/entity/MemberFoodType.java +++ b/src/main/java/com/umc_study/mission_server/member/entity/MemberFoodType.java @@ -3,6 +3,8 @@ import com.umc_study.mission_server.store.entity.FoodType; import jakarta.persistence.*; import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,6 +12,8 @@ @Entity @Table(name = "member_prefer_food_type") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@Builder public class MemberFoodType { @Id diff --git a/src/main/java/com/umc_study/mission_server/member/exception/MemberErrorCode.java b/src/main/java/com/umc_study/mission_server/member/exception/MemberErrorCode.java new file mode 100644 index 0000000..bb5736b --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/exception/MemberErrorCode.java @@ -0,0 +1,16 @@ +package com.umc_study.mission_server.member.exception; + +import com.umc_study.mission_server.common.response.BaseResponseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MemberErrorCode implements BaseResponseCode { + NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER_0404", "사용자를 찾지 못했습니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/umc_study/mission_server/member/exception/MemberException.java b/src/main/java/com/umc_study/mission_server/member/exception/MemberException.java new file mode 100644 index 0000000..1dd29a3 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/exception/MemberException.java @@ -0,0 +1,10 @@ +package com.umc_study.mission_server.member.exception; + +import com.umc_study.mission_server.common.response.BaseResponseCode; +import com.umc_study.mission_server.common.response.GeneralException; + +public class MemberException extends GeneralException { + public MemberException(BaseResponseCode code) { + super(code); + } +} diff --git a/src/main/java/com/umc_study/mission_server/member/service/MemberService.java b/src/main/java/com/umc_study/mission_server/member/service/MemberService.java new file mode 100644 index 0000000..dce2775 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/member/service/MemberService.java @@ -0,0 +1,74 @@ +package com.umc_study.mission_server.member.service; + +import com.umc_study.mission_server.member.dto.MemberResponse; +import com.umc_study.mission_server.member.dto.SignupRequest; +import com.umc_study.mission_server.member.entity.Member; +import com.umc_study.mission_server.member.entity.MemberFoodType; +import com.umc_study.mission_server.member.exception.MemberErrorCode; +import com.umc_study.mission_server.member.exception.MemberException; +import com.umc_study.mission_server.member.repository.MemberRepository; +import com.umc_study.mission_server.store.entity.FoodType; +import com.umc_study.mission_server.store.exception.StoreErrorCode; +import com.umc_study.mission_server.store.exception.StoreException; +import com.umc_study.mission_server.store.repository.FoodTypeRepository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberService { + private final MemberRepository memberRepository; + private final FoodTypeRepository foodTypeRepository; + + @Transactional + public MemberResponse signup(SignupRequest request) { + LocalDateTime now = LocalDateTime.now(); + Member member = Member.builder() + .providerId("test") + .providerType("test") + .agreeTos(request.agreements().tos() ? now : null) + .agreePrivacy(request.agreements().privacy() ? now : null) + .agreeGps(request.agreements().gps() ? now : null) + .agreeMarketing(request.agreements().marketing() ? now : null) + .name(request.name()) + .sex(request.sex()) + .birthday(request.birthday()) + .address1(request.address1()) + .address2(request.address2()) + .registeredAt(now) + .deletedAt(null) + .lastLogin(null) + .nickname(request.nickname()) + .email(request.email()) + .phoneNumber(request.phoneNumber()) + .verifiedPhoneNumber(false) + .notificationAskReply(request.notificationSettings().askReply()) + .notificationNewEvent(request.notificationSettings().newEvent()) + .notificationReviewReply(request.notificationSettings().reviewReply()) + .currentPoint(0L) + .build(); + memberRepository.save(member); + return MemberResponse.from(member); + } + + @Transactional + public void updatePreferFoodTypes(Long memberId, List foodTypes) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + member.getPreferFoodTypes().clear(); + foodTypes.stream() + .map(foodTypeId -> foodTypeRepository + .findById(foodTypeId) + .orElseThrow(() -> new StoreException(StoreErrorCode.FOOD_TYPE_NOT_FOUND))) + .map(foodType -> MemberFoodType.builder() + .member(member) + .foodType(foodType) + .build()) + .forEach(foodType -> member.getPreferFoodTypes().add(foodType)); + } +} diff --git a/src/main/java/com/umc_study/mission_server/mission/controller/MissionController.java b/src/main/java/com/umc_study/mission_server/mission/controller/MissionController.java new file mode 100644 index 0000000..7816de0 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/mission/controller/MissionController.java @@ -0,0 +1,37 @@ +package com.umc_study.mission_server.mission.controller; + +import com.umc_study.mission_server.common.response.ApiResponse; +import com.umc_study.mission_server.mission.dto.CreateMissionRequest; +import com.umc_study.mission_server.mission.dto.MissionResponse; +import com.umc_study.mission_server.mission.entity.Mission; +import com.umc_study.mission_server.mission.service.MissionService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PatchMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class MissionController { + private final MissionService missionService; + + @PostMapping("/missions") + public ApiResponse createMission( + @RequestBody CreateMissionRequest request + ) { + Mission mission = missionService.create(request); + return ApiResponse.ok(MissionResponse.from(mission)); + } + + @PatchMapping("/missions/{id}/start") + public ApiResponse startMission( + @PathVariable Long id + ) { + Mission mission = missionService.start(id); + return ApiResponse.ok(MissionResponse.from(mission)); + } +} diff --git a/src/main/java/com/umc_study/mission_server/mission/dto/CreateMissionRequest.java b/src/main/java/com/umc_study/mission_server/mission/dto/CreateMissionRequest.java new file mode 100644 index 0000000..d2312d4 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/mission/dto/CreateMissionRequest.java @@ -0,0 +1,12 @@ +package com.umc_study.mission_server.mission.dto; + +import java.time.LocalDateTime; + +public record CreateMissionRequest( + Long storeId, + Long memberId, + Long point, + Long money, + LocalDateTime due +) { +} diff --git a/src/main/java/com/umc_study/mission_server/mission/dto/MissionResponse.java b/src/main/java/com/umc_study/mission_server/mission/dto/MissionResponse.java new file mode 100644 index 0000000..7a7f3c2 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/mission/dto/MissionResponse.java @@ -0,0 +1,29 @@ +package com.umc_study.mission_server.mission.dto; + +import com.umc_study.mission_server.mission.entity.Mission; +import com.umc_study.mission_server.mission.entity.MissionState; +import java.time.LocalDateTime; +import lombok.Builder; + +@Builder +public record MissionResponse( + Long id, + Long point, + Long money, + LocalDateTime due, + MissionState state, + Long storeId, + Long memberId +) { + public static MissionResponse from(Mission mission) { + return MissionResponse.builder() + .id(mission.getId()) + .point(mission.getPoint()) + .money(mission.getMoney()) + .due(mission.getDue()) + .state(mission.getState()) + .storeId(mission.getStore().getId()) + .memberId(mission.getMember().getId()) + .build(); + } +} diff --git a/src/main/java/com/umc_study/mission_server/mission/entity/Mission.java b/src/main/java/com/umc_study/mission_server/mission/entity/Mission.java index 309fd21..810603a 100644 --- a/src/main/java/com/umc_study/mission_server/mission/entity/Mission.java +++ b/src/main/java/com/umc_study/mission_server/mission/entity/Mission.java @@ -31,6 +31,7 @@ public class Mission { @Column(nullable = true) private LocalDateTime due; + @Setter @Enumerated(EnumType.STRING) @Column(length = 8, nullable = false) private MissionState state; diff --git a/src/main/java/com/umc_study/mission_server/mission/exception/MissionErrorCode.java b/src/main/java/com/umc_study/mission_server/mission/exception/MissionErrorCode.java new file mode 100644 index 0000000..7f7a989 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/mission/exception/MissionErrorCode.java @@ -0,0 +1,17 @@ +package com.umc_study.mission_server.mission.exception; + +import com.umc_study.mission_server.common.response.BaseResponseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum MissionErrorCode implements BaseResponseCode { + NOT_FOUND(HttpStatus.NOT_FOUND, "MISSION_0404", "미션을 찾지 못했습니다."); + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/umc_study/mission_server/mission/exception/MissionException.java b/src/main/java/com/umc_study/mission_server/mission/exception/MissionException.java new file mode 100644 index 0000000..1c2f70f --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/mission/exception/MissionException.java @@ -0,0 +1,10 @@ +package com.umc_study.mission_server.mission.exception; + +import com.umc_study.mission_server.common.response.BaseResponseCode; +import com.umc_study.mission_server.common.response.GeneralException; + +public class MissionException extends GeneralException { + public MissionException(BaseResponseCode code) { + super(code); + } +} diff --git a/src/main/java/com/umc_study/mission_server/mission/service/MissionService.java b/src/main/java/com/umc_study/mission_server/mission/service/MissionService.java new file mode 100644 index 0000000..fc62090 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/mission/service/MissionService.java @@ -0,0 +1,55 @@ +package com.umc_study.mission_server.mission.service; + +import com.umc_study.mission_server.member.entity.Member; +import com.umc_study.mission_server.member.exception.MemberErrorCode; +import com.umc_study.mission_server.member.exception.MemberException; +import com.umc_study.mission_server.member.repository.MemberRepository; +import com.umc_study.mission_server.mission.dto.CreateMissionRequest; +import com.umc_study.mission_server.mission.entity.Mission; +import com.umc_study.mission_server.mission.entity.MissionState; +import com.umc_study.mission_server.mission.exception.MissionErrorCode; +import com.umc_study.mission_server.mission.exception.MissionException; +import com.umc_study.mission_server.mission.repository.MissionRepository; +import com.umc_study.mission_server.store.entity.Store; +import com.umc_study.mission_server.store.exception.StoreErrorCode; +import com.umc_study.mission_server.store.exception.StoreException; +import com.umc_study.mission_server.store.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MissionService { + private final StoreRepository storeRepository; + private final MemberRepository memberRepository; + private final MissionRepository missionRepository; + + @Transactional + public Mission create(CreateMissionRequest request) { + Store store = storeRepository.findById(request.storeId()) + .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); + + Member member = memberRepository.findById(request.memberId()) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + Mission mission = Mission.builder() + .point(request.point()) + .money(request.money()) + .due(request.due()) + .state(MissionState.PENDING) + .store(store) + .member(member) + .build(); + missionRepository.save(mission); + return mission; + } + + @Transactional + public Mission start(Long missionId) { + Mission mission = missionRepository.findById(missionId) + .orElseThrow(() -> new MissionException(MissionErrorCode.NOT_FOUND)); + mission.setState(MissionState.PROGRESS); + return missionRepository.save(mission); + } +} diff --git a/src/main/java/com/umc_study/mission_server/review/controller/ReviewController.java b/src/main/java/com/umc_study/mission_server/review/controller/ReviewController.java index 3fc65e0..5066f9c 100644 --- a/src/main/java/com/umc_study/mission_server/review/controller/ReviewController.java +++ b/src/main/java/com/umc_study/mission_server/review/controller/ReviewController.java @@ -1,6 +1,7 @@ package com.umc_study.mission_server.review.controller; import com.umc_study.mission_server.common.response.ApiResponse; +import com.umc_study.mission_server.review.dto.CreateReviewRequest; import com.umc_study.mission_server.review.dto.ReviewSearchRequest; import com.umc_study.mission_server.review.domain.Review; import com.umc_study.mission_server.review.service.ReviewService; @@ -12,7 +13,7 @@ import java.util.List; @RestController -@RequestMapping("/api/reviews") +@RequestMapping("/api") @RequiredArgsConstructor public class ReviewController { @@ -23,4 +24,13 @@ public ApiResponse> search(@RequestBody ReviewSearchRequest request List reviews = reviewService.search(request); return ApiResponse.ok(reviews); } + + @PostMapping("/stores/{storeId}/reviews") + public ApiResponse createReview( + @PathVariable Long storeId, + @RequestBody CreateReviewRequest request + ) { + Review review = reviewService.create(storeId, 1L, request); + return ApiResponse.ok(review); + } } diff --git a/src/main/java/com/umc_study/mission_server/review/dto/CreateReviewRequest.java b/src/main/java/com/umc_study/mission_server/review/dto/CreateReviewRequest.java new file mode 100644 index 0000000..0842d13 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/review/dto/CreateReviewRequest.java @@ -0,0 +1,7 @@ +package com.umc_study.mission_server.review.dto; + +public record CreateReviewRequest( + double score, + String content +) { +} diff --git a/src/main/java/com/umc_study/mission_server/review/service/ReviewService.java b/src/main/java/com/umc_study/mission_server/review/service/ReviewService.java index f0ce448..83f7ad2 100644 --- a/src/main/java/com/umc_study/mission_server/review/service/ReviewService.java +++ b/src/main/java/com/umc_study/mission_server/review/service/ReviewService.java @@ -1,6 +1,12 @@ package com.umc_study.mission_server.review.service; import com.umc_study.mission_server.common.Range; +import com.umc_study.mission_server.member.dto.MemberResponse; +import com.umc_study.mission_server.member.entity.Member; +import com.umc_study.mission_server.member.exception.MemberErrorCode; +import com.umc_study.mission_server.member.exception.MemberException; +import com.umc_study.mission_server.member.repository.MemberRepository; +import com.umc_study.mission_server.review.dto.CreateReviewRequest; import com.umc_study.mission_server.review.dto.ReviewSearchRequest; import com.umc_study.mission_server.review.domain.Review; import com.umc_study.mission_server.review.exception.ReviewErrorCode; @@ -8,6 +14,10 @@ import com.umc_study.mission_server.review.repository.ReviewRepository; import com.umc_study.mission_server.review.domain.ReviewSearchQueries; import com.umc_study.mission_server.review.domain.ReviewSearchQueries.ReviewSearchOrderMode; +import com.umc_study.mission_server.store.entity.Store; +import com.umc_study.mission_server.store.exception.StoreErrorCode; +import com.umc_study.mission_server.store.exception.StoreException; +import com.umc_study.mission_server.store.repository.StoreRepository; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -18,8 +28,29 @@ @Service @RequiredArgsConstructor public class ReviewService { + private final MemberRepository memberRepository; + private final StoreRepository storeRepository; private final ReviewRepository reviewRepository; + public Review create(Long storeId, Long memberId, CreateReviewRequest request) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND)); + + Review review = Review.builder() + .score(request.score()) + .content(request.content()) + .reply(null) + .replyAt(null) + .store(store) + .author(member) + .build(); + reviewRepository.save(review); + return review; + } + public List search(ReviewSearchRequest request) { ReviewSearchQueries queries = getQueryFromRequest(request); return reviewRepository.search(queries); diff --git a/src/main/java/com/umc_study/mission_server/store/controller/StoreController.java b/src/main/java/com/umc_study/mission_server/store/controller/StoreController.java new file mode 100644 index 0000000..b6b9093 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/controller/StoreController.java @@ -0,0 +1,27 @@ +package com.umc_study.mission_server.store.controller; + +import com.umc_study.mission_server.common.response.ApiResponse; +import com.umc_study.mission_server.store.dto.UpdateStoreLocationRequest; +import com.umc_study.mission_server.store.service.StoreService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class StoreController { + private final StoreService storeService; + + @PatchMapping("/stores/{storeId}/location") + public ApiResponse updateLocation( + @PathVariable Long storeId, + @RequestBody UpdateStoreLocationRequest request + ) { + storeService.updateStoreLocation(storeId, request.location()); + return ApiResponse.ok(null); + } +} diff --git a/src/main/java/com/umc_study/mission_server/store/dto/UpdateStoreLocationRequest.java b/src/main/java/com/umc_study/mission_server/store/dto/UpdateStoreLocationRequest.java new file mode 100644 index 0000000..7666138 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/dto/UpdateStoreLocationRequest.java @@ -0,0 +1,6 @@ +package com.umc_study.mission_server.store.dto; + +public record UpdateStoreLocationRequest( + String location +) { +} diff --git a/src/main/java/com/umc_study/mission_server/store/entity/Store.java b/src/main/java/com/umc_study/mission_server/store/entity/Store.java index 7eb54b2..66ec300 100644 --- a/src/main/java/com/umc_study/mission_server/store/entity/Store.java +++ b/src/main/java/com/umc_study/mission_server/store/entity/Store.java @@ -5,6 +5,7 @@ @Entity @Getter +@Setter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/src/main/java/com/umc_study/mission_server/store/exception/StoreErrorCode.java b/src/main/java/com/umc_study/mission_server/store/exception/StoreErrorCode.java new file mode 100644 index 0000000..7af7bb2 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/exception/StoreErrorCode.java @@ -0,0 +1,18 @@ +package com.umc_study.mission_server.store.exception; + +import com.umc_study.mission_server.common.response.BaseResponseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum StoreErrorCode implements BaseResponseCode { + FOOD_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE_4040", "음식 종류를 찾을 수 없습니다."), + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE_4041", "가게를 찾을 수 없습니다.") + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/umc_study/mission_server/store/exception/StoreException.java b/src/main/java/com/umc_study/mission_server/store/exception/StoreException.java new file mode 100644 index 0000000..19be8fa --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/exception/StoreException.java @@ -0,0 +1,10 @@ +package com.umc_study.mission_server.store.exception; + +import com.umc_study.mission_server.common.response.BaseResponseCode; +import com.umc_study.mission_server.common.response.GeneralException; + +public class StoreException extends GeneralException { + public StoreException(BaseResponseCode code) { + super(code); + } +} diff --git a/src/main/java/com/umc_study/mission_server/store/repository/FoodTypeRepository.java b/src/main/java/com/umc_study/mission_server/store/repository/FoodTypeRepository.java new file mode 100644 index 0000000..98f3e9a --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/repository/FoodTypeRepository.java @@ -0,0 +1,7 @@ +package com.umc_study.mission_server.store.repository; + +import com.umc_study.mission_server.store.entity.FoodType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FoodTypeRepository extends JpaRepository { +} diff --git a/src/main/java/com/umc_study/mission_server/store/repository/StoreRepository.java b/src/main/java/com/umc_study/mission_server/store/repository/StoreRepository.java new file mode 100644 index 0000000..087df62 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/repository/StoreRepository.java @@ -0,0 +1,7 @@ +package com.umc_study.mission_server.store.repository; + +import com.umc_study.mission_server.store.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StoreRepository extends JpaRepository { +} diff --git a/src/main/java/com/umc_study/mission_server/store/service/StoreService.java b/src/main/java/com/umc_study/mission_server/store/service/StoreService.java new file mode 100644 index 0000000..f18a7e9 --- /dev/null +++ b/src/main/java/com/umc_study/mission_server/store/service/StoreService.java @@ -0,0 +1,23 @@ +package com.umc_study.mission_server.store.service; + +import com.umc_study.mission_server.common.response.GeneralException; +import com.umc_study.mission_server.store.entity.Store; +import com.umc_study.mission_server.store.exception.StoreErrorCode; +import com.umc_study.mission_server.store.exception.StoreException; +import com.umc_study.mission_server.store.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class StoreService { + private final StoreRepository storeRepository; + + @Transactional + public void updateStoreLocation(Long storeId, String storeLocation) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new StoreException(StoreErrorCode.STORE_NOT_FOUND)); + store.setAddress1(storeLocation); + } +}