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

기록하기 API #22

Merged
merged 11 commits into from
Aug 27, 2024
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies {

//AWS S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'javax.xml.bind:jaxb-api:2.3.0'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@EnableFeignClients
@SpringBootApplication
public class DndTravelProjectApplication {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.ProtectedHeader;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -48,6 +49,7 @@
public class AppleOAuthService {
private final AppleClient appleClient;
private final AppleProperties appleProperties;
private final ProtectedHeader protectedHeader;

public AppleIdTokenPayload get(String authorizationCode) {
String idToken = appleClient.getIdToken(
Expand All @@ -64,13 +66,13 @@ private String generateClientSecret() {
LocalDateTime expiration = LocalDateTime.now().plusMinutes(5);

return Jwts.builder()
.setHeaderParam(JwsHeader.KEY_ID, appleProperties.getKeyId())
.setIssuer(appleProperties.getTeamId())
.setAudience(appleProperties.getAudience())
.setSubject(appleProperties.getClientId())
.setExpiration(Date.from(expiration.atZone(ZoneId.systemDefault()).toInstant()))
.setIssuedAt(new Date())
.signWith(getPrivateKey(), SignatureAlgorithm.ES256)
.header().add(protectedHeader.getKeyId(), appleProperties.getKeyId()).and()
.issuer(appleProperties.getTeamId())
.audience().add(appleProperties.getAudience()).and()
.subject(appleProperties.getClientId())
.expiration(Date.from(expiration.atZone(ZoneId.systemDefault()).toInstant()))
.issuedAt(new Date())
.signWith(getPrivateKey())
.compact();
}

Expand Down
21 changes: 19 additions & 2 deletions src/main/java/com/dnd/dndtravel/map/controller/MapController.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package com.dnd.dndtravel.map.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import com.dnd.dndtravel.map.controller.request.RecordRequest;
import com.dnd.dndtravel.map.controller.request.validation.PhotoValidation;
import com.dnd.dndtravel.map.service.MapService;
import com.dnd.dndtravel.map.service.dto.response.RegionResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
Expand All @@ -23,4 +31,13 @@ public RegionResponse map() {
Long memberId = 1L;
return mapService.allRegions(memberId);
}

@PostMapping("/maps/record")
public void memo(
@PhotoValidation @RequestPart("photos") List<MultipartFile> photos,
@RequestPart("recordRequest") RecordRequest recordRequest
) {
Long memberId = 1L;
mapService.recordAttraction(recordRequest.toDto(photos), memberId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.dnd.dndtravel.map.controller.request;

import java.time.LocalDate;
import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import com.dnd.dndtravel.map.controller.request.validation.RegionCondition;
import com.dnd.dndtravel.map.controller.request.validation.RegionEnum;
import com.dnd.dndtravel.map.dto.RecordDto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
//todo 회의 후 제약조건 변경 필요
public record RecordRequest(
@RegionEnum(enumClass = RegionCondition.class)
String region,

@NotBlank(message = "명소 이름은 필수 입력 사항입니다.")
@Pattern(regexp = "^[가-힣]+$", message = "명소 이름은 한글만 입력 가능합니다.")
@Size(max = 50, message = "명소 이름은 50자 이내여야 합니다.")
Copy link
Member

Choose a reason for hiding this comment

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

프로토타입에는 명소명 20자로 되어 있어요!

String attractionName,

@Size(max = 25, message = "메모는 25자 이내여야 합니다.")
String memo,

@NotNull(message = "날짜는 필수 입력 사항입니다.")
LocalDate localDate
Comment on lines +29 to +30
Copy link
Member

Choose a reason for hiding this comment

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

이거 포맷 프로토타입에서 기록할 때 날짜 형식은 2024년7월21일로 되어 있고, 기록 확인할 때는 24.07.21 되어 있네용 ?!
이거에 관련해서 클라이언트 분들이랑 얘기했던 거 같은데 결론이 기억 안나네요 ,,?ㅎ

Copy link
Member Author

Choose a reason for hiding this comment

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

클라에서 포맷팅 해줄겁니다

) {
public RecordDto toDto(List<MultipartFile> photos) {
return RecordDto.builder()
.region(this.region)
.attractionName(this.attractionName)
.photos(photos)
.memo(this.memo)
.dateTime(this.localDate)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dnd.dndtravel.map.controller.request.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;

@Constraint(validatedBy = {PhotoValidator.class})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface PhotoValidation {
String message() default "invalid photo size";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.dnd.dndtravel.map.controller.request.validation;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class PhotoValidator implements ConstraintValidator<PhotoValidation, List<MultipartFile>> {

@Override
public boolean isValid(List<MultipartFile> photos, ConstraintValidatorContext context) {
return photos.size() <= 3;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.dnd.dndtravel.map.controller.request.validation;

import java.util.Arrays;

import lombok.Getter;

@Getter
public enum RegionCondition {
서울("서울"),
부산("부산"),
대구("대구"),
인천("인천"),
광주("광주"),
대전("대전"),
울산("울산"),
경기도("경기도"),
강원도("강원도"),
충청북도("충청북도"),
충남세종("충남·세종"),
전라북도("전라북도"),
전라남도("전라남도"),
경상북도("경상북도"),
경상남도("경상남도"),
제주도("제주도");

private final String value;

RegionCondition(String value) {
this.value = value;
}

public static boolean isMatch(String region) {
return Arrays.stream(RegionCondition.values())
.anyMatch(regionCondition -> regionCondition.getValue().equals(region));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.dnd.dndtravel.map.controller.request.validation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;

@Constraint(validatedBy = {RegionValidator.class})
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RegionEnum {
String message() default "invalid region";
Class<? extends java.lang.Enum<?>> enumClass();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.dnd.dndtravel.map.controller.request.validation;

import java.util.Arrays;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class RegionValidator implements ConstraintValidator<RegionEnum, String> {

private RegionEnum annotation;

@Override
public void initialize(RegionEnum constraintAnnotation) {
this.annotation = constraintAnnotation;
}

@Override
public boolean isValid(String region, ConstraintValidatorContext context) {
// 지역 입력안하면 예외
if (region == null) {
return false;
}

Object[] enumValues = this.annotation.enumClass().getEnumConstants();
if (enumValues == null) {
return false;
}

// 지역 Enum중 하나라도 해당되면 true
return Arrays.stream(enumValues).anyMatch(enumValue -> RegionCondition.isMatch(region));
}
}
39 changes: 39 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/domain/Attraction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.dnd.dndtravel.map.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Attraction {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "region_id")
private Region region;

private String name; // 명소 이름

@Builder
private Attraction(Region region, String name) {
this.region = region;
this.name = name;
}

public static Attraction of(Region region, String attraction) {
return new Attraction(region, attraction);
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/dnd/dndtravel/map/domain/MemberAttraction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.dnd.dndtravel.map.domain;

import java.time.LocalDate;

import com.dnd.dndtravel.member.domain.Member;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class MemberAttraction {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "attraction_id")
private Attraction attraction;

private String memo; // 방문기록 메모
private LocalDate localDate; // 방문 날짜
private String region; // 지역
private int photosCount; // 사진 개수, 필요한가?
//todo 명소 이름도 필요할지도?

@Builder
private MemberAttraction(Member member, Attraction attraction, String memo, LocalDate localDate, String region,
int photosCount) {
this.member = member;
this.attraction = attraction;
this.memo = memo;
this.localDate = localDate;
this.region = region;
this.photosCount = photosCount;
}

public static MemberAttraction of(Member member, Attraction attraction, String memo, LocalDate localDate,
String region) {
return MemberAttraction.builder()
.member(member)
.attraction(attraction)
.memo(memo)
.localDate(localDate)
.region(region)
.build();
}
}
12 changes: 10 additions & 2 deletions src/main/java/com/dnd/dndtravel/map/domain/MemberRegion.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,23 @@ private MemberRegion(Member member, Region region, int visitCount) {
this.visitCount = visitCount;
}

public static MemberRegion of(Member member, Region region, int visitCount) {
public static MemberRegion of(Member member, Region region) {
return MemberRegion.builder()
.member(member)
.region(region)
.visitCount(visitCount)
.visitCount(1)
Comment on lines -43 to +47
Copy link
Member

Choose a reason for hiding this comment

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

visitCount을 지우신 이유가 있나용

Copy link
Member Author

Choose a reason for hiding this comment

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

MemberRegion 객체는 특정 지역에 방문했을때 최초로 생성이되고, 이후에는 visitCount가 add되는 형태라서 이 객체를 생성했을땐 1번 방문했다는거고, 임의의 visitCount를 받아 생성할 일은 없다고 판단했어요

.build();
}

public boolean isVisited() {
return this.visitCount > 0;
}

public boolean isEqualRegion(String regionName) {
return this.region.getName().equals(regionName);
}

public void addVisitCount() {
this.visitCount++;
}
}
Loading
Loading