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

[FEAT] 카테고리별 최신 게시물 조회 API 구현 - #434 #447

Merged
merged 7 commits into from
Nov 15, 2024
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
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
package org.sopt.app.application.playground;

import static org.sopt.app.application.playground.PlaygroundHeaderCreator.*;
import static org.sopt.app.application.playground.PlaygroundHeaderCreator.createAuthorizationHeaderByUserPlaygroundToken;
import static org.sopt.app.application.playground.PlaygroundHeaderCreator.createDefaultHeader;

import lombok.*;
import io.jsonwebtoken.ExpiredJwtException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import org.sopt.app.application.playground.dto.PlayGroundEmploymentResponse;
import org.sopt.app.application.playground.dto.PlaygroundPostInfo.*;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.*;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.sopt.app.application.auth.dto.PlaygroundAuthTokenInfo.RefreshedToken;
import org.sopt.app.common.exception.*;
import org.sopt.app.application.playground.dto.PlayGroundEmploymentResponse;
import org.sopt.app.application.playground.dto.PlayGroundPostCategory;
import org.sopt.app.application.playground.dto.PlaygroundPostInfo.PlaygroundPost;
import org.sopt.app.application.playground.dto.PlaygroundPostInfo.PlaygroundPostResponse;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.ActiveUserIds;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.ActivityCardinalInfo;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.MainView;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.MainViewUser;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.OwnPlaygroundProfile;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.PlaygroundMain;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.PlaygroundProfile;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.UserActiveInfo;
import org.sopt.app.common.exception.BadRequestException;
import org.sopt.app.common.exception.UnauthorizedException;
import org.sopt.app.common.response.ErrorCode;
import org.sopt.app.domain.enums.UserStatus;
import org.sopt.app.presentation.auth.AppAuthRequest.*;
import org.sopt.app.presentation.auth.AppAuthRequest.AccessTokenRequest;
import org.sopt.app.presentation.auth.AppAuthRequest.CodeRequest;
import org.sopt.app.presentation.home.response.EmploymentPostResponse;
import org.sopt.app.presentation.home.response.RecentPostsResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException.BadRequest;
Expand Down Expand Up @@ -161,12 +181,30 @@ public boolean isCurrentGeneration(Long generation) {
return generation.equals(currentGeneration);
}

public List<RecentPostsResponse> getRecentPosts(String playgroundToken) {
final Map<String, String> accessToken = createAuthorizationHeaderByUserPlaygroundToken(playgroundToken);
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<PlayGroundPostCategory> categories = List.of(PlayGroundPostCategory.SOPT_ACTIVITY, PlayGroundPostCategory.FREE, PlayGroundPostCategory.PART);
CompletableFuture<RecentPostsResponse> hotPostFuture = CompletableFuture.supplyAsync(() ->
RecentPostsResponse.of(playgroundClient.getPlaygroundHotPost(accessToken)), executor);
List<CompletableFuture<RecentPostsResponse>> categoryFutures = categories.stream()
.map(category -> CompletableFuture.supplyAsync(() -> playgroundClient.getRecentPosts(accessToken, category.getDisplayName()), executor))
.toList();
List<CompletableFuture<RecentPostsResponse>> allFutures = new ArrayList<>(categoryFutures);
allFutures.addFirst(hotPostFuture);
CompletableFuture<Void> allOf = CompletableFuture.allOf(allFutures.toArray(new CompletableFuture[0]));
return allOf.thenApply(v -> allFutures.stream()
.map(CompletableFuture::join)
.toList())
.join();
}
}

public List<EmploymentPostResponse> getPlaygroundEmploymentPost(String accessToken) {
Map<String, String> requestHeader = createAuthorizationHeaderByUserPlaygroundToken(accessToken);
PlayGroundEmploymentResponse postInfo = playgroundClient.getPlaygroundEmploymentPost(requestHeader,16,10,0);
return postInfo.posts().stream()
.map(EmploymentPostResponse::of)
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.PlaygroundProfile;
import org.sopt.app.application.playground.dto.PlaygroundUserFindCondition;
import org.sopt.app.application.playground.dto.RecommendedFriendInfo.PlaygroundUserIds;
import org.sopt.app.presentation.auth.AppAuthRequest.AccessTokenRequest;
import org.sopt.app.presentation.auth.AppAuthRequest.CodeRequest;
import org.sopt.app.presentation.auth.AppAuthRequest.*;
import org.sopt.app.presentation.home.response.RecentPostsResponse;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.web.bind.annotation.RequestBody;

Expand Down Expand Up @@ -53,6 +53,9 @@ PlaygroundUserIds getPlaygroundUserIdsByCondition(@HeaderMap Map<String, String>
@RequestLine("GET /api/v1/community/posts/hot")
PlaygroundPostResponse getPlaygroundHotPost(@HeaderMap Map<String, String> headers);

@RequestLine("GET /internal/api/v1/community/post/recent?category={category}")
RecentPostsResponse getRecentPosts(@HeaderMap Map<String, String> headers, @Param("category") String category);

@RequestLine("GET /api/v1/community/posts?categoryId={categoryId}&limit={limit}&cursor={cursor}")
PlayGroundEmploymentResponse getPlaygroundEmploymentPost(@HeaderMap Map<String, String> headers,
@Param int categoryId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.app.application.playground.dto;

public enum PlayGroundPostCategory {
SOPT_ACTIVITY("SOPT 활동"),
FREE("자유"),
PART("파트");
private final String displayName;

PlayGroundPostCategory(String displayName) {
this.displayName = displayName;
}

public String getDisplayName() {
return displayName;
}
}

12 changes: 12 additions & 0 deletions src/main/java/org/sopt/app/facade/HomeFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.sopt.app.domain.entity.User;
import org.sopt.app.domain.enums.UserStatus;
import org.sopt.app.presentation.home.HomeDescriptionResponse;
import org.sopt.app.presentation.home.response.RecentPostsResponse;
import org.sopt.app.presentation.home.response.EmploymentPostResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -69,6 +70,17 @@ private boolean isServiceVisibleToUser(AppServiceInfo appServiceInfo, User user)
return false;
}

public List<RecentPostsResponse> getRecentPosts(User user) {
return playgroundAuthService.getRecentPosts(user.getPlaygroundToken()).stream()
.map(post -> RecentPostsResponse.builder()
.id(post.getId())
.title(post.getTitle())
.category(post.getCategory())
.isHotPost(post.isHotPost())
.build()
).toList();
}

public List<EmploymentPostResponse> getHomeEmploymentPost(User user) {
return playgroundAuthService.getPlaygroundEmploymentPost(user.getPlaygroundToken());
}
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/sopt/app/presentation/home/HomeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.sopt.app.application.meeting.MeetingResponse;
import org.sopt.app.domain.entity.User;
import org.sopt.app.facade.HomeFacade;
import org.sopt.app.presentation.home.response.RecentPostsResponse;
import org.sopt.app.presentation.home.response.EmploymentPostResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand Down Expand Up @@ -54,7 +55,21 @@ public ResponseEntity<List<AppServiceEntryStatusResponse>> getAppService(
);
}


@Operation(summary = "최근 채용탭 10개 조회")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "success"),
@ApiResponse(responseCode = "401", description = "token error", content = @Content),
@ApiResponse(responseCode = "500", description = "server error", content = @Content)
})
@GetMapping("/posts")
public ResponseEntity<List<RecentPostsResponse>> getRecentPost(
@AuthenticationPrincipal User user
) {
return ResponseEntity.ok(
homeFacade.getRecentPosts(user));
}

@ApiResponses({
@ApiResponse(responseCode = "200", description = "success"),
@ApiResponse(responseCode = "401", description = "token error", content = @Content),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.sopt.app.presentation.home.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.sopt.app.application.playground.dto.PlaygroundPostInfo.PlaygroundPostResponse;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecentPostsResponse {
private Long id;
private String title;
private String category;
private String content;
private boolean isHotPost;
Copy link
Member

Choose a reason for hiding this comment

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

P1. primitive type은 Getter로 반환할 때 is라는 접두사가 빠지게 됩니다!
현재에도 반환값이 HotPost로 is 접두사가 빠져있는 것 같아요.
그리고 isHotPost라는 필드가 없더라도 클라이언트가 category로 구분이 가능할 것 같은데 어떻게 생각하시나요?
관련 블로그 글

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 이건 몰랐네요! 좋은 정보 감사합니다!
그러면 hot 쪽을 빼는걸로 할까요 ?
image
HotPost가 혹시 카테고리별로 작동될수 있다고 생각해서 일단 넣어두긴 했습니다!

Copy link
Member

Choose a reason for hiding this comment

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

hot post가 카테고리 별로 작용될 것 같아서 그런거라면 유지해도 좋을 것 같아요~


public static RecentPostsResponse of(PlaygroundPostResponse playgroundPostResponse) {
return RecentPostsResponse.builder()
.id(playgroundPostResponse.postId())
.title(playgroundPostResponse.title())
.category("HOT")
.content(playgroundPostResponse.content())
.isHotPost(true)
.build();
Comment on lines +20 to +27
Copy link
Member

Choose a reason for hiding this comment

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

P2: 그럼에도 만약 isHotPost를 사용하시는게 맞다 생각하면 이 메서드 명은 of보다는 팩토리 메서드처럼 객체를 만드는 메서드에 이름을 정해주는 것이 혼란을 방지하기에 좋을 것 같아요!

}
}
Loading