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
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -24,6 +27,23 @@ public ResponseEntity<ApiResponse<Void>> handleException(
);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleException(
MethodArgumentNotValidException ex
) {
// 검사에 실패한 필드와 그에 대한 메시지를 저장하는 Map
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);

GeneralErrorCode code = GeneralErrorCode.BAD_REQUEST;
ApiResponse<Map<String, String>> errorResponse = ApiResponse.error(code, errors);

// 에러 코드, 메시지와 함께 errors를 반환
return ResponseEntity.status(code.getStatus()).body(errorResponse);
}

// 그 외의 정의되지 않은 모든 예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<String>> handleException(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ExistsFoodType, List<Long>> {
private final FoodTypeRepository foodTypeRepository;

@Override
public boolean isValid(List<Long> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<MemberResponse> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.umc_study.mission_server.member.dto;

public record NotificationSettingsDto(
boolean newEvent,
boolean reviewReply,
boolean askReply
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.umc_study.mission_server.member.dto;

public record SignupAgreements(
boolean tos,
boolean privacy,
boolean gps,
boolean marketing
) {
}
Original file line number Diff line number Diff line change
@@ -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
) {
}
Original file line number Diff line number Diff line change
@@ -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<Long> foodTypes
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
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;

@Getter
@Entity
@Table(name = "member_prefer_food_type")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class MemberFoodType {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading