Skip to content

Commit a2ba116

Browse files
authored
Merge pull request #138 from Pinback-Team/feat/#137
feat: 관심 직무 핀 기능 구현
2 parents fc552d5 + 5824007 commit a2ba116

24 files changed

Lines changed: 532 additions & 10 deletions

File tree

api/src/main/java/com/pinback/api/PinbackApiApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
88
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
99
import org.springframework.scheduling.annotation.EnableAsync;
10+
import org.springframework.scheduling.annotation.EnableScheduling;
1011

1112
import com.pinback.application.config.ProfileImageConfig;
1213

@@ -20,6 +21,7 @@
2021
@EnableJpaRepositories("com.pinback.infrastructure")
2122
@EnableJpaAuditing
2223
@EnableAsync
24+
@EnableScheduling
2325
@EnableConfigurationProperties(ProfileImageConfig.class)
2426
public class PinbackApiApplication {
2527
public static void main(String[] args) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.pinback.api.article.controller;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.PathVariable;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
import com.pinback.application.article.dto.response.SharedArticleDetailResponse;
9+
import com.pinback.application.article.dto.response.SharedArticlePageResponse;
10+
import com.pinback.application.article.port.in.GetSharedArticlePort;
11+
import com.pinback.domain.user.entity.User;
12+
import com.pinback.shared.annotation.CurrentUser;
13+
import com.pinback.shared.dto.ResponseDto;
14+
15+
import io.swagger.v3.oas.annotations.Operation;
16+
import io.swagger.v3.oas.annotations.Parameter;
17+
import io.swagger.v3.oas.annotations.tags.Tag;
18+
import lombok.RequiredArgsConstructor;
19+
20+
@RestController
21+
@RequestMapping("/api/v3/articles/shared/job")
22+
@RequiredArgsConstructor
23+
@Tag(name = "Shared Article", description = "관심 직무 핀 관리 API")
24+
public class SharedArticleController {
25+
private final GetSharedArticlePort getSharedArticlePort;
26+
27+
@Operation(summary = "관심 직무 아티클 전체 조회", description = "관심 직무 핀의 아티클 목록을 조회합니다")
28+
@GetMapping
29+
public ResponseDto<SharedArticlePageResponse> getAllSharedArticle(
30+
@Parameter(hidden = true) @CurrentUser User user
31+
) {
32+
SharedArticlePageResponse response = getSharedArticlePort.getAllSharedArticle(user);
33+
return ResponseDto.ok(response);
34+
}
35+
36+
@Operation(summary = "관심 직무 아티클 상세 조회", description = "관심 직무 아티클의 상세 정보를 조회합니다")
37+
@GetMapping("/{articleId}")
38+
public ResponseDto<SharedArticleDetailResponse> getSharedArticleDetail(
39+
@Parameter(hidden = true) @CurrentUser User user,
40+
@Parameter(description = "아티클 ID") @PathVariable Long articleId
41+
) {
42+
SharedArticleDetailResponse response = getSharedArticlePort.getSharedArticleDetail(user, articleId);
43+
return ResponseDto.ok(response);
44+
}
45+
46+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.pinback.application.article.dto;
2+
3+
import com.pinback.domain.article.entity.Article;
4+
import com.pinback.domain.category.enums.CategoryColor;
5+
6+
public record SharedArticleDto(
7+
long articleId,
8+
String url,
9+
String title,
10+
String thumbnailUrl,
11+
String memo,
12+
String ownerName,
13+
Long categoryId,
14+
String categoryName,
15+
CategoryColor categoryColor
16+
) {
17+
public static SharedArticleDto from(Article article) {
18+
return new SharedArticleDto(
19+
article.getId(),
20+
article.getUrl(),
21+
article.getTitle(),
22+
article.getThumbnail(),
23+
article.getMemo(),
24+
article.getUser().getUsername(),
25+
article.getCategory().getId(),
26+
article.getCategory().getName(),
27+
article.getCategory().getColor()
28+
);
29+
}
30+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.pinback.application.article.dto.response;
2+
3+
import com.pinback.domain.article.entity.Article;
4+
5+
public record SharedArticleDetailResponse(
6+
long articleId,
7+
String ownerName,
8+
String memo
9+
) {
10+
public static SharedArticleDetailResponse from(Article article) {
11+
return new SharedArticleDetailResponse(
12+
article.getId(),
13+
article.getUser().getUsername(),
14+
article.getMemo()
15+
);
16+
}
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.pinback.application.article.dto.response;
2+
3+
import java.util.List;
4+
5+
public record SharedArticlePageResponse(
6+
String job,
7+
List<SharedArticleResponse> articles
8+
) {
9+
public static SharedArticlePageResponse of(String job, List<SharedArticleResponse> articles) {
10+
return new SharedArticlePageResponse(job, articles);
11+
}
12+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.pinback.application.article.dto.response;
2+
3+
import com.pinback.application.article.dto.SharedArticleDto;
4+
import com.pinback.application.category.dto.response.CategoryResponse;
5+
6+
public record SharedArticleResponse(
7+
long articleId,
8+
String url,
9+
String title,
10+
String thumbnailUrl,
11+
String memo,
12+
String ownerName,
13+
CategoryResponse category
14+
) {
15+
public static SharedArticleResponse from(SharedArticleDto dto) {
16+
return new SharedArticleResponse(
17+
dto.articleId(),
18+
dto.url(),
19+
dto.title(),
20+
dto.thumbnailUrl(),
21+
dto.memo(),
22+
dto.ownerName(),
23+
CategoryResponse.of(dto.categoryId(), dto.categoryName(), dto.categoryColor().toString())
24+
);
25+
}
26+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.pinback.application.article.port.in;
2+
3+
import com.pinback.application.article.dto.response.SharedArticleDetailResponse;
4+
import com.pinback.application.article.dto.response.SharedArticlePageResponse;
5+
import com.pinback.domain.user.entity.User;
6+
7+
public interface GetSharedArticlePort {
8+
SharedArticlePageResponse getAllSharedArticle(User user);
9+
10+
SharedArticleDetailResponse getSharedArticleDetail(User user, Long articleId);
11+
}

application/src/main/java/com/pinback/application/article/port/out/ArticleGetServicePort.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.pinback.application.article.port.out;
22

33
import java.time.LocalDateTime;
4+
import java.util.List;
45
import java.util.Optional;
56

67
import org.springframework.data.domain.Page;
@@ -12,9 +13,11 @@
1213
import com.pinback.application.article.dto.ArticlesWithUnreadCountDto;
1314
import com.pinback.application.article.dto.RemindArticlesWithCountDto;
1415
import com.pinback.application.article.dto.RemindArticlesWithCountDtoV2;
16+
import com.pinback.application.article.dto.SharedArticleDto;
1517
import com.pinback.domain.article.entity.Article;
1618
import com.pinback.domain.category.entity.Category;
1719
import com.pinback.domain.user.entity.User;
20+
import com.pinback.domain.user.enums.Job;
1821

1922
public interface ArticleGetServicePort {
2023
Optional<Article> findRecentByUser(User user);
@@ -52,4 +55,6 @@ ArticlesWithCountDto findAllByCategoryAndReadStatus(User user, Category category
5255
PageRequest pageRequest);
5356

5457
ArticleCountInfoDtoV3 findAllCountByCategoryV3(User user, Category category);
58+
59+
List<SharedArticleDto> findTopListByJob(Job job);
5560
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.pinback.application.article.port.out;
2+
3+
import java.util.List;
4+
5+
import com.pinback.application.article.dto.SharedArticleDto;
6+
7+
public interface SharedArticleRedisPort {
8+
List<SharedArticleDto> getSharedArticlesByJob(String job);
9+
10+
List<Long> getSharedArticleIdsByJob(String job);
11+
12+
void saveSharedArticles(String job, List<SharedArticleDto> articles);
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.pinback.application.article.usecase.query;
2+
3+
import java.util.List;
4+
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.transaction.annotation.Transactional;
7+
8+
import com.pinback.application.article.dto.SharedArticleDto;
9+
import com.pinback.application.article.dto.response.SharedArticleDetailResponse;
10+
import com.pinback.application.article.dto.response.SharedArticlePageResponse;
11+
import com.pinback.application.article.dto.response.SharedArticleResponse;
12+
import com.pinback.application.article.port.in.GetSharedArticlePort;
13+
import com.pinback.application.article.port.out.ArticleGetServicePort;
14+
import com.pinback.application.article.port.out.SharedArticleRedisPort;
15+
import com.pinback.application.common.exception.SharedArticleNotFoundException;
16+
import com.pinback.domain.article.entity.Article;
17+
import com.pinback.domain.user.entity.User;
18+
19+
import lombok.RequiredArgsConstructor;
20+
21+
@Service
22+
@RequiredArgsConstructor
23+
@Transactional(readOnly = true)
24+
public class GetSharedArticleUsecase implements GetSharedArticlePort {
25+
private final SharedArticleRedisPort sharedArticleRedisPort;
26+
private final ArticleGetServicePort articleGetServicePort;
27+
28+
@Override
29+
public SharedArticlePageResponse getAllSharedArticle(User user) {
30+
if (user.getJob() == null) {
31+
return SharedArticlePageResponse.of(null, List.of());
32+
}
33+
34+
String jobKey = user.getJob().getKey();
35+
List<SharedArticleDto> articles = sharedArticleRedisPort.getSharedArticlesByJob(jobKey);
36+
37+
String jobValue = user.getJob().getValue();
38+
if (articles == null || articles.isEmpty()) {
39+
return SharedArticlePageResponse.of(jobValue, List.of());
40+
}
41+
42+
List<SharedArticleResponse> articleResponses = articles.stream()
43+
.map(SharedArticleResponse::from)
44+
.toList();
45+
return SharedArticlePageResponse.of(jobValue, articleResponses);
46+
}
47+
48+
@Override
49+
public SharedArticleDetailResponse getSharedArticleDetail(User user, Long articleId) {
50+
String jobKey = user.getJob().getKey();
51+
List<Long> sharedIds = sharedArticleRedisPort.getSharedArticleIdsByJob(jobKey);
52+
53+
if (!sharedIds.contains(articleId)) {
54+
throw new SharedArticleNotFoundException();
55+
}
56+
57+
Article article = articleGetServicePort.findById(articleId);
58+
59+
return SharedArticleDetailResponse.from(article);
60+
}
61+
}

0 commit comments

Comments
 (0)