Skip to content

Commit

Permalink
Merge pull request #22 from dnd-side-project/feat/#19
Browse files Browse the repository at this point in the history
기록하기 API
  • Loading branch information
youngreal authored Aug 27, 2024
2 parents 801b72e + 8f16ff7 commit c1bdef0
Show file tree
Hide file tree
Showing 22 changed files with 537 additions and 23 deletions.
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자 이내여야 합니다.")
String attractionName,

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

@NotNull(message = "날짜는 필수 입력 사항입니다.")
LocalDate localDate
) {
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)
.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

0 comments on commit c1bdef0

Please sign in to comment.