Skip to content

Commit

Permalink
Merge pull request #34 from f-lab-edu/feature/33
Browse files Browse the repository at this point in the history
[#33] 피드를 여행일지로 묶기
  • Loading branch information
f-lab-moony authored Feb 11, 2024
2 parents d79fd08 + c7a99aa commit b2353e7
Show file tree
Hide file tree
Showing 27 changed files with 519 additions and 76 deletions.
6 changes: 2 additions & 4 deletions src/main/java/com/stoury/config/ContextConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ public PasswordEncoder passwordEncoder() {
public GeoApiContext geoApiContext() {
return new GeoApiContext.Builder()
.apiKey(apiKey)
.connectTimeout(5, TimeUnit.SECONDS)
.connectTimeout(2, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.retryTimeout(2, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.maxRetries(5)
.maxRetries(3)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMapping
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/feeds/popular/*")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/comments/**")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/rank/**")).permitAll()
.requestMatchers(new MvcRequestMatcher.Builder(introspector).pattern(HttpMethod.GET, "/diaries/**")).permitAll()
.anyRequest().authenticated())
.exceptionHandling(exHandler -> exHandler
.authenticationEntryPoint((request, response, authException) -> response
Expand Down
11 changes: 3 additions & 8 deletions src/main/java/com/stoury/controller/CommentController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,11 @@
import com.stoury.dto.member.AuthenticatedMember;
import com.stoury.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;
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.RestController;

@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -50,8 +45,8 @@ public List<ChildCommentResponse> getChildComments(@PathVariable Long commentId,
}

@DeleteMapping("/comments/{commentId}")
public ResponseEntity<Object> deleteComment(@PathVariable Long commentId) {
commentService.deleteComment(commentId);
return ResponseEntity.ok().build();
public void deleteComment(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long commentId) {
commentService.deleteCommentIfOwner(commentId, authenticatedMember.getId());
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/stoury/controller/DiaryController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.stoury.controller;

import com.stoury.dto.diary.DiaryCreateRequest;
import com.stoury.dto.diary.DiaryPageResponse;
import com.stoury.dto.diary.DiaryResponse;
import com.stoury.dto.member.AuthenticatedMember;
import com.stoury.service.DiaryService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
public class DiaryController {
private final DiaryService diaryService;

@PostMapping("/diaries")
public DiaryResponse createDiary(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@RequestBody(required = true) DiaryCreateRequest diaryCreateRequest) {
return diaryService.createDiary(diaryCreateRequest, authenticatedMember.getId());
}

@GetMapping("/diaries/member/{memberId}")
public DiaryPageResponse getMemberDiaries(@PathVariable Long memberId,
@RequestParam(required = false, defaultValue = "0") int pageNo) {
return diaryService.getMemberDiaries(memberId, pageNo);
}

@DeleteMapping("/diaries/{diaryId}")
public void cancelDiary(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long diaryId) {
diaryService.cancelDiaryIfOwner(diaryId, authenticatedMember.getId());
}
}
20 changes: 18 additions & 2 deletions src/main/java/com/stoury/controller/ExceptionController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@

import com.stoury.dto.ErrorResponse;
import com.stoury.exception.AlreadyLikedFeedException;
import com.stoury.exception.diary.DiaryCreateException;
import com.stoury.exception.diary.DiarySearchException;
import com.stoury.exception.feed.FeedCreateException;
import com.stoury.exception.feed.FeedSearchException;
import com.stoury.exception.feed.FeedUpdateException;
import com.stoury.exception.location.GeocodeApiException;
import com.stoury.exception.member.MemberCreateException;
import com.stoury.exception.member.MemberDeleteException;
import com.stoury.exception.member.MemberSearchException;
import com.stoury.exception.member.MemberUpdateException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = {FeedCreateException.class, FeedUpdateException.class, AlreadyLikedFeedException.class})
@ExceptionHandler(value = {
FeedCreateException.class, FeedUpdateException.class,
AlreadyLikedFeedException.class,
IllegalArgumentException.class,
DiaryCreateException.class,
MemberCreateException.class, MemberDeleteException.class, MemberUpdateException.class})
public ResponseEntity<ErrorResponse> handle400(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ErrorResponse.of(ex.getMessage()));
}

@ExceptionHandler(value = {FeedSearchException.class, MemberSearchException.class})
@ExceptionHandler(value = {FeedSearchException.class, MemberSearchException.class, DiarySearchException.class})
public ResponseEntity<ErrorResponse> handle404(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse.of(ex.getMessage()));
}

@ExceptionHandler(value = {GeocodeApiException.class})
public ResponseEntity<ErrorResponse> handle500(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResponse.of(ex.getMessage()));
}
}
13 changes: 7 additions & 6 deletions src/main/java/com/stoury/controller/FeedController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.stoury.dto.member.AuthenticatedMember;
import com.stoury.service.FeedService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
Expand Down Expand Up @@ -46,13 +45,15 @@ public List<FeedResponse> getFeedsOfMember(@PathVariable Long memberId,
}

@PutMapping("/feeds/{feedId}")
public FeedResponse updateFeed(@PathVariable Long feedId, @RequestBody FeedUpdateRequest feedUpdateRequest) {
return feedService.updateFeed(feedId, feedUpdateRequest);
public FeedResponse updateFeed(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long feedId,
@RequestBody FeedUpdateRequest feedUpdateRequest) {
return feedService.updateFeedIfOwner(feedId, feedUpdateRequest, authenticatedMember.getId());
}

@DeleteMapping("/feeds/{feedId}")
public ResponseEntity<Object> deleteFeed(@PathVariable Long feedId) {
feedService.deleteFeed(feedId);
return ResponseEntity.ok().build();
public void deleteFeed(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long feedId) {
feedService.deleteFeedIfOwner(feedId, authenticatedMember.getId());
}
}
13 changes: 4 additions & 9 deletions src/main/java/com/stoury/controller/LikeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.stoury.dto.member.AuthenticatedMember;
import com.stoury.service.LikeService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -16,18 +15,14 @@ public class LikeController {
private final LikeService likeService;

@PostMapping("/like/feed/{feedId}")
public ResponseEntity<Object> likeFeed(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long feedId) {
public void likeFeed(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long feedId) {
likeService.like(authenticatedMember.getId(), feedId);

return ResponseEntity.ok().build();
}

@DeleteMapping("/like/feed/{feedId}")
public ResponseEntity<Object> cancelLikeFeed(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long feedId) {
public void cancelLikeFeed(@AuthenticationPrincipal AuthenticatedMember authenticatedMember,
@PathVariable Long feedId) {
likeService.likeCancel(authenticatedMember.getId(), feedId);

return ResponseEntity.ok().build();
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/stoury/domain/Diary.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.stoury.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "DIARY")
public class Diary {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(optional = false)
private Member member;

@JoinColumn(name = "DIARY_ID")
@OneToMany(fetch = FetchType.LAZY, orphanRemoval = false)
private List<Feed> feeds = new ArrayList<>();

@Column(name = "TITLE", length = 50)
private String title;

@JoinColumn(name = "THUMBNAIL_ID")
@OneToOne(optional = false)
private GraphicContent thumbnail;

public Diary(Member member, List<Feed> feeds, String title, GraphicContent thumbnail) {
this.member = member;
this.feeds = feeds;
this.title = title;
this.thumbnail = thumbnail;
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/stoury/domain/GraphicContent.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stoury.domain;

import com.stoury.utils.FileUtils;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
Expand Down Expand Up @@ -27,4 +28,8 @@ public GraphicContent(String path, int sequence) {
this.path = path;
this.sequence = sequence;
}

public boolean isImage() {
return FileUtils.isImage(path);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/stoury/dto/diary/DiaryCreateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.stoury.dto.diary;

import java.util.List;

public record DiaryCreateRequest(String title, List<Long> feedIds, Long thumbnailId) {
}
16 changes: 16 additions & 0 deletions src/main/java/com/stoury/dto/diary/DiaryPageResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.stoury.dto.diary;

import com.stoury.domain.Diary;
import org.springframework.data.domain.Page;

import java.util.List;

public record DiaryPageResponse(List<SimpleDiaryResponse> diaries, int pageNo, boolean hasNext) {
public static DiaryPageResponse from(Page<Diary> page) {
List<SimpleDiaryResponse> diaries = page.getContent().stream().map(SimpleDiaryResponse::from).toList();
int pageNo = page.getNumber();
boolean hasNext = page.hasNext();

return new DiaryPageResponse(diaries, pageNo, hasNext);
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/stoury/dto/diary/DiaryResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.stoury.dto.diary;

import com.stoury.domain.Diary;
import com.stoury.dto.feed.FeedResponse;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;

public record DiaryResponse(Long id, Long memberId, String title, String thumbnailPath, Map<Long, List<FeedResponse>> feeds,
LocalDate startDate, LocalDate endDate,
String city, String country, long likes) {

public static DiaryResponse from(Diary diary, List<FeedResponse> feeds) {
List<FeedResponse> sortedFeeds = feeds.stream().sorted(Comparator.comparing(FeedResponse::createdAt)).toList();
Map<Long, List<FeedResponse>> dailyFeeds = getDailyFeeds(sortedFeeds);

FeedResponse firstFeed = sortedFeeds.get(0);

FeedResponse lastFeed = sortedFeeds.get(sortedFeeds.size() - 1);

long likesSum = dailyFeeds.values().stream()
.flatMap(Collection::stream)
.map(FeedResponse::likes)
.mapToLong(Long::longValue)
.sum();

return new DiaryResponse(
diary.getId(),
diary.getMember().getId(),
diary.getTitle(),
diary.getThumbnail().getPath(),
dailyFeeds,
firstFeed.createdAt().toLocalDate(),
lastFeed.createdAt().toLocalDate(),
firstFeed.location().city(),
firstFeed.location().country(),
likesSum
);
}

private static Map<Long, List<FeedResponse>> getDailyFeeds(List<FeedResponse> sortedFeeds) {
LocalDate startDate = sortedFeeds.get(0).createdAt().toLocalDate();

Map<Long, List<FeedResponse>> dailyFeeds = new TreeMap<>();

for (FeedResponse feed : sortedFeeds) {
long day = ChronoUnit.DAYS.between(startDate, feed.createdAt().toLocalDate()) + 1;

dailyFeeds.computeIfAbsent(day, d -> new ArrayList<>()).add(feed);
}

return dailyFeeds;
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/stoury/dto/diary/SimpleDiaryResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.stoury.dto.diary;

import com.stoury.domain.Diary;

public record SimpleDiaryResponse(Long id, String thumbnail, String title, Long memberId) {
public static SimpleDiaryResponse from(Diary diary) {
return new SimpleDiaryResponse(diary.getId(), diary.getThumbnail().getPath(),
diary.getTitle(), diary.getMember().getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.stoury.exception.diary;

public class DiaryCreateException extends RuntimeException {
public DiaryCreateException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.stoury.exception.diary;

public class DiarySearchException extends RuntimeException {
}
10 changes: 0 additions & 10 deletions src/main/java/com/stoury/exception/feed/FeedDeleteException.java

This file was deleted.

11 changes: 11 additions & 0 deletions src/main/java/com/stoury/repository/DiaryRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.stoury.repository;

import com.stoury.domain.Diary;
import com.stoury.domain.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface DiaryRepository extends JpaRepository<Diary, Long> {
Page<Diary> findByMember(Member member, Pageable page);
}
Loading

0 comments on commit b2353e7

Please sign in to comment.