Skip to content

Commit 318768b

Browse files
authored
Merge pull request #150 from Pinback-Team/dev
3차 스프린트 prod 업데이트
2 parents 9516548 + a72256e commit 318768b

97 files changed

Lines changed: 2221 additions & 73 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
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

12+
import com.pinback.application.config.JobImageConfig;
1113
import com.pinback.application.config.ProfileImageConfig;
1214

1315
@SpringBootApplication(scanBasePackages = {
@@ -20,7 +22,8 @@
2022
@EnableJpaRepositories("com.pinback.infrastructure")
2123
@EnableJpaAuditing
2224
@EnableAsync
23-
@EnableConfigurationProperties(ProfileImageConfig.class)
25+
@EnableScheduling
26+
@EnableConfigurationProperties({ProfileImageConfig.class, JobImageConfig.class})
2427
public class PinbackApiApplication {
2528
public static void main(String[] args) {
2629
SpringApplication.run(PinbackApiApplication.class, args);

api/src/main/java/com/pinback/api/article/controller/ArticleControllerV3.java

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
package com.pinback.api.article.controller;
22

3+
import java.time.LocalDateTime;
4+
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.PathVariable;
37
import org.springframework.web.bind.annotation.PostMapping;
48
import org.springframework.web.bind.annotation.RequestBody;
59
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RequestParam;
611
import org.springframework.web.bind.annotation.RestController;
712

813
import com.pinback.api.article.dto.request.ArticleCreateRequest;
14+
import com.pinback.application.article.dto.query.PageQuery;
15+
import com.pinback.application.article.dto.response.ArticleCountInfoResponse;
16+
import com.pinback.application.article.dto.response.ArticleDetailResponseV3;
17+
import com.pinback.application.article.dto.response.ArticlesPageResponseV3;
18+
import com.pinback.application.article.dto.response.GetAllArticlesResponseV3;
19+
import com.pinback.application.article.dto.response.TodayRemindResponseV3;
920
import com.pinback.application.article.port.in.CreateArticlePort;
21+
import com.pinback.application.article.port.in.GetArticlePort;
1022
import com.pinback.domain.user.entity.User;
1123
import com.pinback.infrastructure.article.service.ArticleUpdateService;
1224
import com.pinback.shared.annotation.CurrentUser;
@@ -26,6 +38,7 @@
2638
@Tag(name = "ArticleV3", description = "아티클 관리 API V3")
2739
public class ArticleControllerV3 {
2840
private final CreateArticlePort createArticlePort;
41+
private final GetArticlePort getArticlePort;
2942
private final ArticleUpdateService articleMetadataUpdateService;
3043

3144
@Operation(summary = "아티클 생성v3", description = "url에서 썸네일과 제목을 추출하여 새로운 아티클을 생성합니다")
@@ -38,6 +51,89 @@ public ResponseDto<Void> createArticle(
3851
return ResponseDto.ok();
3952
}
4053

54+
@Operation(summary = "아티클 상세 조회 V3", description = "아티클의 상세 정보를 조회합니다")
55+
@GetMapping("/{articleId}")
56+
public ResponseDto<ArticleDetailResponseV3> getArticleDetail(
57+
@Parameter(hidden = true) @CurrentUser User user,
58+
@Parameter(description = "아티클 ID") @PathVariable Long articleId
59+
) {
60+
ArticleDetailResponseV3 response = getArticlePort.getArticleDetailWithMetadata(user, articleId);
61+
return ResponseDto.ok(response);
62+
}
63+
64+
@Operation(summary = "리마인드 아티클 조회 v3", description = "오늘 리마인드할 아티클을 읽음/안읽음 상태별로 조회합니다.")
65+
@GetMapping("/remind")
66+
public ResponseDto<TodayRemindResponseV3> getRemindArticlesV3(
67+
@Parameter(hidden = true) @CurrentUser User user,
68+
@Parameter(description = "현재 시간", example = "2026-02-13T10:00:00") @RequestParam LocalDateTime now,
69+
@Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean readStatus,
70+
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
71+
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
72+
) {
73+
PageQuery query = new PageQuery(page, size);
74+
TodayRemindResponseV3 response = getArticlePort.getRemindArticlesV3(user, now, readStatus, query);
75+
return ResponseDto.ok(response);
76+
}
77+
78+
@Operation(summary = "리마인드 아티클 읽음/안읽음 개수 조회 v3", description = "오늘 리마인드할 아티클의 읽음/안읽음 개수를 반환합니다.")
79+
@GetMapping("/remind/count")
80+
public ResponseDto<ArticleCountInfoResponse> getRemindArticlesInfo(
81+
@Parameter(hidden = true) @CurrentUser User user,
82+
@Parameter(description = "현재 시간", example = "2026-02-13T10:00:00") @RequestParam LocalDateTime now
83+
) {
84+
ArticleCountInfoResponse response = getArticlePort.getRemindArticlesInfo(user, now);
85+
return ResponseDto.ok(response);
86+
}
87+
88+
@Operation(summary = "나의 북마크 전체 아티클 조회 V3", description = "사용자의 모든 아티클을 페이징으로 조회합니다.")
89+
@GetMapping
90+
public ResponseDto<GetAllArticlesResponseV3> getAllArticles(
91+
@Parameter(hidden = true) @CurrentUser User user,
92+
@Parameter(description = "읽음 상태 (생략: 전체보기, false: 안읽음)", example = "false") @RequestParam(name = "read-status", required = false) Boolean readStatus,
93+
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
94+
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
95+
) {
96+
PageQuery query = new PageQuery(page, size);
97+
GetAllArticlesResponseV3 response = getArticlePort.getAllArticlesV3(user, readStatus, query);
98+
return ResponseDto.ok(response);
99+
}
100+
101+
@Operation(summary = "나의 북마크 아티클 전체보기/안읽음 개수 조회 v3", description = "나의 북마크 아티클의 전체보기/안읽음 개수를 반환합니다.")
102+
@GetMapping("/count")
103+
public ResponseDto<ArticleCountInfoResponse> getArticlesInfo(
104+
@Parameter(hidden = true) @CurrentUser User user
105+
) {
106+
ArticleCountInfoResponse response = getArticlePort.getAllArticlesInfo(user);
107+
return ResponseDto.ok(response);
108+
}
109+
110+
@Operation(summary = "나의 북마크 카테고리별 아티클 조회 V3", description = "특정 카테고리의 아티클 전체 목록을 조회합니다.")
111+
@GetMapping("/category")
112+
public ResponseDto<ArticlesPageResponseV3> getAllArticlesByCategory(
113+
@Parameter(hidden = true) @CurrentUser User user,
114+
@Parameter(description = "카테고리 ID") @RequestParam(name = "category-id") Long categoryId,
115+
@Parameter(description = "읽음 상태 (생략시: 전체보기, false: 안읽음)", example = "false") @RequestParam(name = "read-status", required = false) Boolean readStatus,
116+
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
117+
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
118+
) {
119+
PageQuery query = new PageQuery(page, size);
120+
ArticlesPageResponseV3 response = getArticlePort.getAllArticlesByCategoryV3(user, categoryId, readStatus,
121+
query);
122+
return ResponseDto.ok(response);
123+
}
124+
125+
@Operation(summary = "나의 북마크 카테고리별 아티클 전체보기/안읽음 개수 조회 v3", description = "나의 북마크에서 특정 카테고리 아티클의 전체보기/안읽음 개수를 반환합니다.")
126+
@GetMapping("/category/count")
127+
public ResponseDto<ArticleCountInfoResponse> getArticlesInfoByCategory(
128+
@Parameter(hidden = true) @CurrentUser User user,
129+
@Parameter(description = "카테고리 ID") @RequestParam(name = "category-id") Long categoryId
130+
131+
) {
132+
ArticleCountInfoResponse response = getArticlePort.getAllArticlesInfoByCategoryV3(user, categoryId);
133+
return ResponseDto.ok(response);
134+
}
135+
136+
// 기존 아티클 메타데이터 처리 후 삭제 예정
41137
@PostMapping("/metadata")
42138
public ResponseDto<Void> migrateMetadata() {
43139
articleMetadataUpdateService.migrateMissingMetadata();
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.api.auth.dto.request;
2+
3+
import java.time.LocalTime;
4+
5+
import com.fasterxml.jackson.annotation.JsonFormat;
6+
import com.pinback.application.auth.dto.SignUpCommandV3;
7+
8+
import io.swagger.v3.oas.annotations.media.Schema;
9+
import jakarta.validation.constraints.NotBlank;
10+
import jakarta.validation.constraints.NotNull;
11+
12+
public record SignUpRequestV3(
13+
@NotBlank(message = "이메일은 비어있을 수 없습니다.")
14+
String email,
15+
16+
@Schema(description = "기본 알림 시간", example = "08:30", pattern = "HH:mm")
17+
@JsonFormat(pattern = "HH:mm")
18+
@NotNull(message = "리마인드 시간은 비어있을 수 없습니다.")
19+
LocalTime remindDefault,
20+
21+
@NotNull(message = "알림 정보는 비어있을 수 없습니다.")
22+
String fcmToken,
23+
24+
@NotBlank(message = "직무는 비어있을 수 없습니다.")
25+
String job
26+
) {
27+
public SignUpCommandV3 toCommand() {
28+
return new SignUpCommandV3(email, remindDefault, fcmToken, job);
29+
}
30+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.pinback.api.category.controller;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.PatchMapping;
5+
import org.springframework.web.bind.annotation.PathVariable;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestBody;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
import com.pinback.api.category.dto.request.CreateCategoryRequestV3;
12+
import com.pinback.api.category.dto.request.UpdateCategoryRequestV3;
13+
import com.pinback.application.category.dto.response.CategoryDetailResponseV3;
14+
import com.pinback.application.category.dto.response.CreateCategoryResponseV3;
15+
import com.pinback.application.category.dto.response.UpdateCategoryResponseV3;
16+
import com.pinback.application.category.port.in.CreateCategoryPort;
17+
import com.pinback.application.category.port.in.GetCategoryPort;
18+
import com.pinback.application.category.port.in.UpdateCategoryPort;
19+
import com.pinback.domain.user.entity.User;
20+
import com.pinback.shared.annotation.CurrentUser;
21+
import com.pinback.shared.dto.ResponseDto;
22+
23+
import io.swagger.v3.oas.annotations.Operation;
24+
import io.swagger.v3.oas.annotations.Parameter;
25+
import io.swagger.v3.oas.annotations.tags.Tag;
26+
import jakarta.validation.Valid;
27+
import lombok.RequiredArgsConstructor;
28+
29+
@RestController
30+
@RequestMapping("/api/v3/categories")
31+
@RequiredArgsConstructor
32+
@Tag(name = "CategoryV3", description = "카테고리 관리 API V3")
33+
public class CategoryControllerV3 {
34+
private final CreateCategoryPort createCategoryPort;
35+
private final UpdateCategoryPort updateCategoryPort;
36+
private final GetCategoryPort getCategoryPort;
37+
38+
@Operation(summary = "카테고리 생성 V3", description = "공개여부 설정을 추가하여 새로운 카테고리를 생성합니다")
39+
@PostMapping
40+
public ResponseDto<CreateCategoryResponseV3> createCategoryV3(
41+
@Parameter(hidden = true) @CurrentUser User user,
42+
@Valid @RequestBody CreateCategoryRequestV3 request
43+
) {
44+
CreateCategoryResponseV3 response = createCategoryPort.createCategoryV3(user, request.toCommand());
45+
return ResponseDto.ok(response);
46+
}
47+
48+
@Operation(summary = "카테고리 수정 V3", description = "공개여부 설정을 추가하여 카테고리 정보를 수정합니다")
49+
@PatchMapping("/{categoryId}")
50+
public ResponseDto<UpdateCategoryResponseV3> updateCategoryV3(
51+
@Parameter(hidden = true) @CurrentUser User user,
52+
@Parameter(description = "카테고리 ID") @PathVariable Long categoryId,
53+
@Valid @RequestBody UpdateCategoryRequestV3 request
54+
) {
55+
UpdateCategoryResponseV3 response = updateCategoryPort.updateCategoryV3(user, categoryId, request.toCommand());
56+
return ResponseDto.ok(response);
57+
}
58+
59+
@Operation(summary = "카테고리 상세 조회 V3", description = "공개여부를 포함하여 대시보드에서 사용할 카테고리 정보를 조회합니다")
60+
@GetMapping("/{categoryId}")
61+
public ResponseDto<CategoryDetailResponseV3> getCategoriesForDashboardV3(
62+
@Parameter(hidden = true) @CurrentUser User user,
63+
@Parameter(description = "카테고리 ID") @PathVariable Long categoryId
64+
) {
65+
CategoryDetailResponseV3 response = getCategoryPort.getCategoryDetail(user, categoryId);
66+
return ResponseDto.ok(response);
67+
68+
}
69+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.pinback.api.category.dto.request;
2+
3+
import com.pinback.application.category.dto.command.CreateCategoryCommandV3;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import jakarta.validation.constraints.NotBlank;
7+
import jakarta.validation.constraints.Size;
8+
9+
@Schema(description = "카테고리 생성 요청 V3")
10+
public record CreateCategoryRequestV3(
11+
@Schema(description = "카테고리 이름", example = "개발")
12+
@NotBlank(message = "카테고리 이름은 필수입니다")
13+
@Size(max = 50, message = "카테고리 이름은 50자 이하로 입력해주세요")
14+
String categoryName,
15+
@Schema(description = "카테고리 공개여부", example = "true")
16+
Boolean isPublic
17+
) {
18+
public CreateCategoryCommandV3 toCommand() {
19+
return new CreateCategoryCommandV3(categoryName, isPublic);
20+
}
21+
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.pinback.api.category.dto.request;
2+
3+
import com.pinback.application.category.dto.command.UpdateCategoryCommandV3;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import jakarta.validation.constraints.NotBlank;
7+
import jakarta.validation.constraints.NotNull;
8+
import jakarta.validation.constraints.Size;
9+
10+
@Schema(description = "카테고리 수정 요청 V3")
11+
public record UpdateCategoryRequestV3(
12+
@Schema(description = "카테고리 이름", example = "수정된 개발")
13+
@NotBlank(message = "카테고리 이름은 필수입니다")
14+
@Size(max = 50, message = "카테고리 이름은 50자 이하로 입력해주세요")
15+
String categoryName,
16+
@Schema(description = "카테고리 공개여부", example = "false")
17+
@NotNull(message = "공개 여부는 필수 항목입니다(true/false)")
18+
Boolean isPublic
19+
) {
20+
public UpdateCategoryCommandV3 toCommand() {
21+
return new UpdateCategoryCommandV3(categoryName, isPublic);
22+
}
23+
}

api/src/main/java/com/pinback/api/config/filter/JwtAuthenticationFilter.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletExce
101101
path.startsWith("/oauth/callback") ||
102102
path.startsWith("/login/google") ||
103103
path.startsWith("/login/oauth2/code/google") ||
104-
path.startsWith("/api/v2/auth/signup")
104+
path.startsWith("/api/v2/auth/signup") ||
105+
path.startsWith("/api/v3/auth/signup") ||
106+
path.startsWith("/api/v3/auth/google") ||
107+
path.startsWith("/api/v3/enums/jobs")
105108
;
106109
}
107110
}

api/src/main/java/com/pinback/api/config/security/SecurityConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5454
.requestMatchers(
5555
"/api/v1/auth/token",
5656
"/api/v2/auth/google",
57-
"/api/v2/auth/signup"
57+
"/api/v2/auth/signup",
58+
"/api/v3/auth/signup",
59+
"/api/v3/auth/google"
5860
).permitAll()
5961

6062
.requestMatchers(
@@ -67,6 +69,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
6769
"/login/oauth2/code/google"
6870
).permitAll()
6971

72+
.requestMatchers(
73+
"/api/v3/enums/jobs"
74+
).permitAll()
75+
7076
.anyRequest().authenticated()
7177
)
7278
.formLogin(AbstractHttpConfigurer::disable)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.pinback.api.constant;
2+
3+
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.RequestMapping;
5+
import org.springframework.web.bind.annotation.RestController;
6+
7+
import com.pinback.application.constant.dto.response.JobsResponse;
8+
import com.pinback.application.constant.port.in.ConstantManagementPort;
9+
import com.pinback.shared.dto.ResponseDto;
10+
11+
import lombok.RequiredArgsConstructor;
12+
13+
@RestController
14+
@RequiredArgsConstructor
15+
@RequestMapping("/api/v3/enums")
16+
public class ConstantController {
17+
private final ConstantManagementPort constantManagementPort;
18+
19+
@GetMapping("/jobs")
20+
public ResponseDto<JobsResponse> getJobs() {
21+
JobsResponse response = constantManagementPort.getJobs();
22+
return ResponseDto.ok(response);
23+
}
24+
25+
}

0 commit comments

Comments
 (0)