From b200d9479ee0760226a7d2987999e29e8fd2d90f Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Wed, 14 Jan 2026 00:26:22 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20lostitemarticle=20controller=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/ArticleApi.java | 116 ----------- .../article/controller/ArticleController.java | 78 -------- .../controller/LostItemArticleApi.java | 155 +++++++++++++++ .../controller/LostItemArticleController.java | 113 +++++++++++ .../article/controller/LostItemReportApi.java | 2 +- .../article/service/ArticleService.java | 106 +--------- .../service/LostItemArticleService.java | 184 ++++++++++++++++++ 7 files changed, 454 insertions(+), 300 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java index e49fccd64..898e0868c 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java @@ -97,21 +97,6 @@ ResponseEntity searchArticles( @UserId Integer userId ); - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - } - ) - @Operation(summary = "분실물 게시글 검색") - @GetMapping("/lost-item/search") - ResponseEntity searchArticles( - @RequestParam String query, - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @IpAddress String ipAddress, - @UserId Integer userId - ); - @ApiResponses( value = { @ApiResponse(responseCode = "200") @@ -122,105 +107,4 @@ ResponseEntity searchArticles( ResponseEntity getArticlesHotKeyword( @RequestParam Integer count ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "분실물 게시글 목록 조회") - @GetMapping("/lost-item") - ResponseEntity getLostItemArticles( - @RequestParam(required = false) String type, - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @UserId Integer userId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "분실물 게시글 목록 조회 V2", description = """ - ### 분실물 게시글 목록 조회 V2 변경점 - - Request Param 추가: foundStatus (ALL, FOUND, NOT_FOUND) - - ALL : 모든 분실물 게시글 조회 (Default) - - FOUND : '주인 찾음' 상태인 게시글 조회 - - NOT_FOUND : '찾는 중' 상태인 게시글 조회 - """) - @GetMapping("/lost-item/v2") - ResponseEntity getLostItemArticlesV2( - @RequestParam(required = false) String type, - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, - @UserId Integer userId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "분실물 게시글 단건 조회") - @GetMapping("/lost-item/{id}") - ResponseEntity getLostItemArticle( - @Parameter(in = PATH) @PathVariable("id") Integer articleId, - @UserId Integer userId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "201"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "422", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "분실물 게시글 등록") - @PostMapping("/lost-item") - ResponseEntity createLostItemArticle( - @Auth(permit = {STUDENT, COUNCIL}) Integer userId, - @RequestBody @Valid LostItemArticlesRequest lostItemArticlesRequest - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "204"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "422", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "분실물 게시글 삭제") - @DeleteMapping("/lost-item/{id}") - ResponseEntity deleteLostItemArticle( - @PathVariable("id") Integer articleId, - @Auth(permit = {STUDENT, COUNCIL}) Integer councilId - ); - - @ApiResponseCodes({ - NO_CONTENT, - FORBIDDEN_AUTHOR, - DUPLICATE_FOUND_STATUS - }) - @Operation(summary = "분실물 게시글 찾음 처리") - @PostMapping("/lost-item/{id}/found") - ResponseEntity markLostItemArticleAsFound( - @PathVariable("id") Integer articleId, - @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId - ); - - @ApiResponseCodes({ - OK - }) - @Operation(summary = "주인 찾음 상태인 분실물 게시글 총 개수 조회") - @GetMapping("/lost-item/found/count") - ResponseEntity getFoundLostItemArticlesCount(); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java index 00aee8e99..a2900cc8d 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java @@ -38,7 +38,6 @@ public class ArticleController implements ArticleApi { private final ArticleService articleService; - private final LostItemFoundService lostItemFoundService; @GetMapping("/{id}") public ResponseEntity getArticle( @@ -80,19 +79,6 @@ public ResponseEntity searchArticles( return ResponseEntity.ok().body(foundArticles); } - @GetMapping("/lost-item/search") - public ResponseEntity searchArticles( - @RequestParam String query, - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @IpAddress String ipAddress, - @UserId Integer userId - ) { - LostItemArticlesResponse foundArticles = articleService.searchLostItemArticles(query, page, limit, ipAddress, - userId); - return ResponseEntity.ok().body(foundArticles); - } - @GetMapping("/hot/keyword") public ResponseEntity getArticlesHotKeyword( @RequestParam Integer count @@ -100,68 +86,4 @@ public ResponseEntity getArticlesHotKeyword( ArticleHotKeywordResponse response = articleService.getArticlesHotKeyword(count); return ResponseEntity.ok().body(response); } - - @GetMapping("/lost-item") - public ResponseEntity getLostItemArticles( - @RequestParam(required = false) String type, - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @UserId Integer userId - ) { - LostItemArticlesResponse response = articleService.getLostItemArticles(type, page, limit, userId); - return ResponseEntity.ok().body(response); - } - - @GetMapping("/lost-item/v2") - public ResponseEntity getLostItemArticlesV2( - @RequestParam(required = false) String type, - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, - @UserId Integer userId - ) { - LostItemArticlesResponse response = articleService.getLostItemArticlesV2(type, page, limit, userId, foundStatus); - return ResponseEntity.ok().body(response); - } - - @GetMapping("/lost-item/{id}") - public ResponseEntity getLostItemArticle( - @PathVariable("id") Integer articleId, - @UserId Integer userId - ) { - return ResponseEntity.ok().body(articleService.getLostItemArticle(articleId, userId)); - } - - @PostMapping("/lost-item") - public ResponseEntity createLostItemArticle( - @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer studentId, - @RequestBody @Valid LostItemArticlesRequest lostItemArticlesRequest - ) { - LostItemArticleResponse response = articleService.createLostItemArticle(studentId, lostItemArticlesRequest); - return ResponseEntity.status(HttpStatus.CREATED).body(response); - } - - @DeleteMapping("/lost-item/{id}") - public ResponseEntity deleteLostItemArticle( - @PathVariable("id") Integer articleId, - @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId - ) { - articleService.deleteLostItemArticle(articleId, userId); - return ResponseEntity.noContent().build(); - } - - @PostMapping("/lost-item/{id}/found") - public ResponseEntity markLostItemArticleAsFound( - @PathVariable("id") Integer articleId, - @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId - ) { - lostItemFoundService.markAsFound(userId, articleId); - return ResponseEntity.noContent().build(); - } - - @GetMapping("/lost-item/found/count") - public ResponseEntity getFoundLostItemArticlesCount() { - FoundLostItemArticleCountResponse response = lostItemFoundService.countFoundArticles(); - return ResponseEntity.ok().body(response); - } } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java new file mode 100644 index 000000000..8b0248179 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -0,0 +1,155 @@ +package in.koreatech.koin.domain.community.article.controller; + +import static in.koreatech.koin.domain.user.model.UserType.*; +import static in.koreatech.koin.global.code.ApiResponseCode.*; +import static in.koreatech.koin.global.code.ApiResponseCode.OK; +import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; +import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; +import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; +import in.koreatech.koin.global.auth.Auth; +import in.koreatech.koin.global.auth.UserId; +import in.koreatech.koin.global.code.ApiResponseCodes; +import in.koreatech.koin.global.ipaddress.IpAddress; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "(Normal) LostItem Articles: 분실물 게시글", description = "분실물 게시글 정보를 관리한다") +@RequestMapping("/articles") +public interface LostItemArticleApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + } + ) + @Operation(summary = "분실물 게시글 검색") + @GetMapping("/lost-item/search") + ResponseEntity searchArticles( + @RequestParam String query, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @IpAddress String ipAddress, + @UserId Integer userId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "분실물 게시글 목록 조회") + @GetMapping("/lost-item") + ResponseEntity getLostItemArticles( + @RequestParam(required = false) String type, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @UserId Integer userId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "분실물 게시글 목록 조회 V2", description = """ + ### 분실물 게시글 목록 조회 V2 변경점 + - Request Param 추가: foundStatus (ALL, FOUND, NOT_FOUND) + - ALL : 모든 분실물 게시글 조회 (Default) + - FOUND : '주인 찾음' 상태인 게시글 조회 + - NOT_FOUND : '찾는 중' 상태인 게시글 조회 + """) + @GetMapping("/lost-item/v2") + ResponseEntity getLostItemArticlesV2( + @RequestParam(required = false) String type, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, + @UserId Integer userId + ); + + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "분실물 게시글 단건 조회") + @GetMapping("/lost-item/{id}") + ResponseEntity getLostItemArticle( + @Parameter(in = PATH) @PathVariable("id") Integer articleId, + @UserId Integer userId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "201"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "422", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "분실물 게시글 등록") + @PostMapping("/lost-item") + ResponseEntity createLostItemArticle( + @Auth(permit = {STUDENT, COUNCIL}) Integer userId, + @RequestBody @Valid LostItemArticlesRequest lostItemArticlesRequest + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "204"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "422", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "분실물 게시글 삭제") + @DeleteMapping("/lost-item/{id}") + ResponseEntity deleteLostItemArticle( + @PathVariable("id") Integer articleId, + @Auth(permit = {STUDENT, COUNCIL}) Integer councilId + ); + + @ApiResponseCodes({ + NO_CONTENT, + FORBIDDEN_AUTHOR, + DUPLICATE_FOUND_STATUS + }) + @Operation(summary = "분실물 게시글 찾음 처리") + @PostMapping("/lost-item/{id}/found") + ResponseEntity markLostItemArticleAsFound( + @PathVariable("id") Integer articleId, + @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId + ); + + @ApiResponseCodes({ + OK + }) + @Operation(summary = "주인 찾음 상태인 분실물 게시글 총 개수 조회") + @GetMapping("/lost-item/found/count") + ResponseEntity getFoundLostItemArticlesCount(); +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java new file mode 100644 index 000000000..ee3792613 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java @@ -0,0 +1,113 @@ +package in.koreatech.koin.domain.community.article.controller; + +import static in.koreatech.koin.domain.user.model.UserType.*; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; +import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; +import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.service.LostItemArticleService; +import in.koreatech.koin.domain.community.article.service.LostItemFoundService; +import in.koreatech.koin.global.auth.Auth; +import in.koreatech.koin.global.auth.UserId; +import in.koreatech.koin.global.ipaddress.IpAddress; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/articles") +public class LostItemArticleController implements LostItemArticleApi { + + private final LostItemArticleService lostItemArticleService; + private final LostItemFoundService lostItemFoundService; + + @GetMapping("/lost-item/search") + public ResponseEntity searchArticles( + @RequestParam String query, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @IpAddress String ipAddress, + @UserId Integer userId + ) { + LostItemArticlesResponse foundArticles = lostItemArticleService.searchLostItemArticles(query, page, limit, ipAddress, + userId); + return ResponseEntity.ok().body(foundArticles); + } + + @GetMapping("/lost-item") + public ResponseEntity getLostItemArticles( + @RequestParam(required = false) String type, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @UserId Integer userId + ) { + LostItemArticlesResponse response = lostItemArticleService.getLostItemArticles(type, page, limit, userId); + return ResponseEntity.ok().body(response); + } + + @GetMapping("/lost-item/v2") + public ResponseEntity getLostItemArticlesV2( + @RequestParam(required = false) String type, + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, + @UserId Integer userId + ) { + LostItemArticlesResponse response = lostItemArticleService.getLostItemArticlesV2(type, page, limit, userId, foundStatus); + return ResponseEntity.ok().body(response); + } + + @GetMapping("/lost-item/{id}") + public ResponseEntity getLostItemArticle( + @PathVariable("id") Integer articleId, + @UserId Integer userId + ) { + return ResponseEntity.ok().body(lostItemArticleService.getLostItemArticle(articleId, userId)); + } + + @PostMapping("/lost-item") + public ResponseEntity createLostItemArticle( + @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer studentId, + @RequestBody @Valid LostItemArticlesRequest lostItemArticlesRequest + ) { + LostItemArticleResponse response = lostItemArticleService.createLostItemArticle(studentId, lostItemArticlesRequest); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @DeleteMapping("/lost-item/{id}") + public ResponseEntity deleteLostItemArticle( + @PathVariable("id") Integer articleId, + @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId + ) { + lostItemArticleService.deleteLostItemArticle(articleId, userId); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/lost-item/{id}/found") + public ResponseEntity markLostItemArticleAsFound( + @PathVariable("id") Integer articleId, + @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId + ) { + lostItemFoundService.markAsFound(userId, articleId); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/lost-item/found/count") + public ResponseEntity getFoundLostItemArticlesCount() { + FoundLostItemArticleCountResponse response = lostItemFoundService.countFoundArticles(); + return ResponseEntity.ok().body(response); + } +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemReportApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemReportApi.java index 9afb70e1b..44db8448e 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemReportApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemReportApi.java @@ -15,7 +15,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -@Tag(name = "(Normal) Articles: 게시글", description = "게시글 정보를 관리한다") +@Tag(name = "(Normal) LostItem Articles: 분실물 게시글", description = "분실물 게시글 정보를 관리한다") public interface LostItemReportApi { @Operation(summary = "분실물 게시글 신고하기") diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java index df235c6be..e4d82e57b 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java @@ -51,7 +51,6 @@ @Transactional(readOnly = true) public class ArticleService { - public static final int LOST_ITEM_BOARD_ID = 14; public static final int NOTICE_BOARD_ID = 4; private static final int HOT_ARTICLE_BEFORE_DAYS = 30; private static final int HOT_ARTICLE_LIMIT = 10; @@ -63,16 +62,13 @@ public class ArticleService { Sort.Order.desc("id") ); - private final ApplicationEventPublisher eventPublisher; + private final ArticleRepository articleRepository; - private final LostItemArticleRepository lostItemArticleRepository; private final BoardRepository boardRepository; private final HotArticleRepository hotArticleRepository; private final ArticleHitUserRepository articleHitUserRepository; - private final UserRepository userRepository; private final Clock clock; private final S3Client s3Client; - private final KeywordExtractor keywordExtractor; private final PopularKeywordTracker popularKeywordTracker; private final KeywordRankingManager keywordRankingManager; @@ -161,103 +157,12 @@ public ArticleHotKeywordResponse getArticlesHotKeyword(int count) { return ArticleHotKeywordResponse.from(topKeywords); } - @Transactional - public LostItemArticlesResponse searchLostItemArticles(String query, Integer page, Integer limit, - String ipAddress, Integer userId) { - verifyQueryLength(query); - Criteria criteria = Criteria.of(page, limit); - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), NATIVE_ARTICLES_SORT); - Page
articles = articleRepository.findAllByBoardIdAndTitleContaining(LOST_ITEM_BOARD_ID, query, - pageRequest); - - String[] keywords = query.split("\\s+"); - - for (String keyword : keywords) { - popularKeywordTracker.updateKeywordWeight(ipAddress, keyword); - } - - return LostItemArticlesResponse.of(articles, criteria, userId); - } - private void verifyQueryLength(String query) { if (query.length() >= MAXIMUM_SEARCH_LENGTH) { throw new KoinIllegalArgumentException("검색어의 최대 길이를 초과했습니다."); } } - public LostItemArticlesResponse getLostItemArticles(String type, Integer page, Integer limit, Integer userId) { - Long total = articleRepository.countBy(); - Criteria criteria = Criteria.of(page, limit, total.intValue()); - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), ARTICLES_SORT); - Page
articles; - - if (type == null) { - articles = articleRepository.findAllByBoardId(LOST_ITEM_BOARD_ID, pageRequest); - } else { - articles = articleRepository.findAllByLostItemArticleType(type, pageRequest); - } - - return LostItemArticlesResponse.of(articles, criteria, userId); - } - - public LostItemArticlesResponse getLostItemArticlesV2(String type, Integer page, Integer limit, Integer userId, - LostItemFoundStatus foundStatus) { - Boolean foundStatusFilter = Optional.ofNullable(foundStatus) - .map(LostItemFoundStatus::getQueryStatus) - .orElse(null); - - Long total = lostItemArticleRepository.countLostItemArticlesWithFilters(type, foundStatusFilter, - LOST_ITEM_BOARD_ID); - - Criteria criteria = Criteria.of(page, limit, total.intValue()); - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), ARTICLES_SORT); - - List
articles = lostItemArticleRepository.findLostItemArticlesWithFilters(LOST_ITEM_BOARD_ID, type, - foundStatusFilter, pageRequest); - Page
articlePage = new PageImpl<>(articles, pageRequest, total); - - return LostItemArticlesResponse.of(articlePage, criteria, userId); - } - - public LostItemArticleResponse getLostItemArticle(Integer articleId, Integer userId) { - Article article = articleRepository.getById(articleId); - setPrevNextArticle(LOST_ITEM_BOARD_ID, article); - - boolean isMine = false; - User author = article.getLostItemArticle().getAuthor(); - if (author != null && Objects.equals(author.getId(), userId)) { - isMine = true; - } - - return LostItemArticleResponse.of(article, isMine); - } - - @Transactional - public LostItemArticleResponse createLostItemArticle(Integer userId, LostItemArticlesRequest requests) { - Board lostItemBoard = boardRepository.getById(LOST_ITEM_BOARD_ID); - User user = userRepository.getById(userId); - List
newArticles = new ArrayList<>(); - - for (var article : requests.articles()) { - Article lostItemArticle = Article.createLostItemArticle(article, lostItemBoard, user); - articleRepository.save(lostItemArticle); - newArticles.add(lostItemArticle); - } - - sendKeywordNotification(newArticles, userId); - return LostItemArticleResponse.of(newArticles.get(0), true); - } - - @Transactional - public void deleteLostItemArticle(Integer articleId, Integer userId) { - Article foundArticle = articleRepository.getById(articleId); - User author = foundArticle.getLostItemArticle().getAuthor(); - if (!Objects.equals(author.getId(), userId)) { - throw AuthorizationException.withDetail("userId: " + userId); - } - foundArticle.delete(); - } - private void setPrevNextArticle(Integer boardId, Article article) { Article prevArticle; Article nextArticle; @@ -282,13 +187,4 @@ private Board getBoard(Integer boardId, Article article) { } return boardRepository.getById(boardId); } - - private void sendKeywordNotification(List
articles, Integer authorId) { - List keywordEvents = keywordExtractor.matchKeyword(articles, authorId); - if (!keywordEvents.isEmpty()) { - for (ArticleKeywordEvent event : keywordEvents) { - eventPublisher.publishEvent(event); - } - } - } } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java new file mode 100644 index 000000000..c9296a6ab --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java @@ -0,0 +1,184 @@ +package in.koreatech.koin.domain.community.article.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.common.event.ArticleKeywordEvent; +import in.koreatech.koin.common.model.Criteria; +import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; +import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; +import in.koreatech.koin.domain.community.article.exception.ArticleBoardMisMatchException; +import in.koreatech.koin.domain.community.article.model.Article; +import in.koreatech.koin.domain.community.article.model.Board; +import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.redis.PopularKeywordTracker; +import in.koreatech.koin.domain.community.article.repository.ArticleRepository; +import in.koreatech.koin.domain.community.article.repository.BoardRepository; +import in.koreatech.koin.domain.community.article.repository.LostItemArticleRepository; +import in.koreatech.koin.domain.community.util.KeywordExtractor; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.repository.UserRepository; +import in.koreatech.koin.global.auth.exception.AuthorizationException; +import in.koreatech.koin.global.exception.custom.KoinIllegalArgumentException; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class LostItemArticleService { + + public static final int LOST_ITEM_BOARD_ID = 14; + private static final int MAXIMUM_SEARCH_LENGTH = 100; + public static final int NOTICE_BOARD_ID = 4; + private static final Sort NATIVE_ARTICLES_SORT = Sort.by( + Sort.Order.desc("id") + ); + private static final Sort ARTICLES_SORT = Sort.by( + Sort.Order.desc("id") + ); + + private final ArticleRepository articleRepository; + private final LostItemArticleRepository lostItemArticleRepository; + private final BoardRepository boardRepository; + private final UserRepository userRepository; + private final PopularKeywordTracker popularKeywordTracker; + private final ApplicationEventPublisher eventPublisher; + private final KeywordExtractor keywordExtractor; + + @Transactional + public LostItemArticlesResponse searchLostItemArticles(String query, Integer page, Integer limit, + String ipAddress, Integer userId) { + if (query.length() >= MAXIMUM_SEARCH_LENGTH) { + throw new KoinIllegalArgumentException("검색어의 최대 길이를 초과했습니다."); + } + Criteria criteria = Criteria.of(page, limit); + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), NATIVE_ARTICLES_SORT); + Page
articles = articleRepository.findAllByBoardIdAndTitleContaining(LOST_ITEM_BOARD_ID, query, + pageRequest); + + String[] keywords = query.split("\\s+"); + + for (String keyword : keywords) { + popularKeywordTracker.updateKeywordWeight(ipAddress, keyword); + } + + return LostItemArticlesResponse.of(articles, criteria, userId); + } + + public LostItemArticlesResponse getLostItemArticles(String type, Integer page, Integer limit, Integer userId) { + Long total = articleRepository.countBy(); + Criteria criteria = Criteria.of(page, limit, total.intValue()); + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), ARTICLES_SORT); + Page
articles; + + if (type == null) { + articles = articleRepository.findAllByBoardId(LOST_ITEM_BOARD_ID, pageRequest); + } else { + articles = articleRepository.findAllByLostItemArticleType(type, pageRequest); + } + + return LostItemArticlesResponse.of(articles, criteria, userId); + } + + public LostItemArticlesResponse getLostItemArticlesV2(String type, Integer page, Integer limit, Integer userId, + LostItemFoundStatus foundStatus) { + Boolean foundStatusFilter = Optional.ofNullable(foundStatus) + .map(LostItemFoundStatus::getQueryStatus) + .orElse(null); + + Long total = lostItemArticleRepository.countLostItemArticlesWithFilters(type, foundStatusFilter, + LOST_ITEM_BOARD_ID); + + Criteria criteria = Criteria.of(page, limit, total.intValue()); + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), ARTICLES_SORT); + + List
articles = lostItemArticleRepository.findLostItemArticlesWithFilters(LOST_ITEM_BOARD_ID, type, + foundStatusFilter, pageRequest); + Page
articlePage = new PageImpl<>(articles, pageRequest, total); + + return LostItemArticlesResponse.of(articlePage, criteria, userId); + } + + public LostItemArticleResponse getLostItemArticle(Integer articleId, Integer userId) { + Article article = articleRepository.getById(articleId); + setPrevNextArticle(LOST_ITEM_BOARD_ID, article); + + boolean isMine = false; + User author = article.getLostItemArticle().getAuthor(); + if (author != null && Objects.equals(author.getId(), userId)) { + isMine = true; + } + + return LostItemArticleResponse.of(article, isMine); + } + + @Transactional + public LostItemArticleResponse createLostItemArticle(Integer userId, LostItemArticlesRequest requests) { + Board lostItemBoard = boardRepository.getById(LOST_ITEM_BOARD_ID); + User user = userRepository.getById(userId); + List
newArticles = new ArrayList<>(); + + for (var article : requests.articles()) { + Article lostItemArticle = Article.createLostItemArticle(article, lostItemBoard, user); + articleRepository.save(lostItemArticle); + newArticles.add(lostItemArticle); + } + + sendKeywordNotification(newArticles, userId); + return LostItemArticleResponse.of(newArticles.get(0), true); + } + + @Transactional + public void deleteLostItemArticle(Integer articleId, Integer userId) { + Article foundArticle = articleRepository.getById(articleId); + User author = foundArticle.getLostItemArticle().getAuthor(); + if (!Objects.equals(author.getId(), userId)) { + throw AuthorizationException.withDetail("userId: " + userId); + } + foundArticle.delete(); + } + + private void setPrevNextArticle(Integer boardId, Article article) { + Article prevArticle; + Article nextArticle; + if (boardId != null) { + Board board = getBoard(boardId, article); + prevArticle = articleRepository.getPreviousArticle(board, article); + nextArticle = articleRepository.getNextArticle(board, article); + } else { + prevArticle = articleRepository.getPreviousAllArticle(article); + nextArticle = articleRepository.getNextAllArticle(article); + } + article.setPrevNextArticles(prevArticle, nextArticle); + } + + private Board getBoard(Integer boardId, Article article) { + if (boardId == null) { + boardId = article.getBoard().getId(); + } + if (!Objects.equals(boardId, article.getBoard().getId()) + && (!article.getBoard().isNotice() || boardId != NOTICE_BOARD_ID)) { + throw ArticleBoardMisMatchException.withDetail("boardId: " + boardId + ", articleId: " + article.getId()); + } + return boardRepository.getById(boardId); + } + + private void sendKeywordNotification(List
articles, Integer authorId) { + List keywordEvents = keywordExtractor.matchKeyword(articles, authorId); + if (!keywordEvents.isEmpty()) { + for (ArticleKeywordEvent event : keywordEvents) { + eventPublisher.publishEvent(event); + } + } + } +} From 86d86be6500f1459f288fa104062fe28d0434947 Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Wed, 14 Jan 2026 01:45:29 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EB=B6=84=EC=8B=A4=EB=AC=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=20=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/ArticleApi.java | 13 ------ .../article/controller/ArticleController.java | 14 ------- .../controller/LostItemArticleApi.java | 29 +++++++------ .../controller/LostItemArticleController.java | 17 ++++++-- .../model/filter/LostItemAuthorFilter.java | 29 +++++++++++++ .../model/filter/LostItemCategoryFilter.java | 18 ++++++++ .../{ => filter}/LostItemFoundStatus.java | 2 +- .../model/filter/LostItemSortType.java | 13 ++++++ .../LostItemArticleCustomRepository.java | 7 +++- .../LostItemArticleCustomRepositoryImpl.java | 42 +++++++++++++++---- .../article/service/ArticleService.java | 13 ------ .../service/LostItemArticleService.java | 21 +++++++--- .../koin/global/code/ApiResponseCode.java | 1 + 13 files changed, 146 insertions(+), 73 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemAuthorFilter.java create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemCategoryFilter.java rename src/main/java/in/koreatech/koin/domain/community/article/model/{ => filter}/LostItemFoundStatus.java (79%) create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemSortType.java diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java index 898e0868c..e0bd951fb 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleApi.java @@ -1,32 +1,20 @@ package in.koreatech.koin.domain.community.article.controller; -import static in.koreatech.koin.domain.user.model.UserType.*; -import static in.koreatech.koin.global.code.ApiResponseCode.*; import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH; import java.util.List; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import in.koreatech.koin.domain.community.article.dto.ArticleHotKeywordResponse; import in.koreatech.koin.domain.community.article.dto.ArticleResponse; import in.koreatech.koin.domain.community.article.dto.ArticlesResponse; -import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.dto.HotArticleItemResponse; -import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; -import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; -import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; -import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; -import in.koreatech.koin.global.auth.Auth; import in.koreatech.koin.global.auth.UserId; -import in.koreatech.koin.global.code.ApiResponseCodes; import in.koreatech.koin.global.ipaddress.IpAddress; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -35,7 +23,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; @Tag(name = "(Normal) Articles: 게시글", description = "게시글 정보를 관리한다") @RequestMapping("/articles") diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java index a2900cc8d..9f43694dc 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/ArticleController.java @@ -1,16 +1,10 @@ package in.koreatech.koin.domain.community.article.controller; -import static in.koreatech.koin.domain.user.model.UserType.*; - import java.util.List; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -18,18 +12,10 @@ import in.koreatech.koin.domain.community.article.dto.ArticleHotKeywordResponse; import in.koreatech.koin.domain.community.article.dto.ArticleResponse; import in.koreatech.koin.domain.community.article.dto.ArticlesResponse; -import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.dto.HotArticleItemResponse; -import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; -import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; -import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; -import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; import in.koreatech.koin.domain.community.article.service.ArticleService; -import in.koreatech.koin.domain.community.article.service.LostItemFoundService; -import in.koreatech.koin.global.auth.Auth; import in.koreatech.koin.global.auth.UserId; import in.koreatech.koin.global.ipaddress.IpAddress; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java index 8b0248179..715894f36 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -18,7 +18,10 @@ import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; -import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.filter.LostItemSortType; import in.koreatech.koin.global.auth.Auth; import in.koreatech.koin.global.auth.UserId; import in.koreatech.koin.global.code.ApiResponseCodes; @@ -66,29 +69,31 @@ ResponseEntity getLostItemArticles( @UserId Integer userId ); - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) + @ApiResponseCodes({ + OK, + UNAUTHORIZED_USER, + }) @Operation(summary = "분실물 게시글 목록 조회 V2", description = """ ### 분실물 게시글 목록 조회 V2 변경점 - - Request Param 추가: foundStatus (ALL, FOUND, NOT_FOUND) - - ALL : 모든 분실물 게시글 조회 (Default) - - FOUND : '주인 찾음' 상태인 게시글 조회 - - NOT_FOUND : '찾는 중' 상태인 게시글 조회 + - Request Param 추가: foundStatus, category, sort, authorType + - 내 게시물 필터를 설정할 경우, 토큰을 포함하여 요청하지 않으면 401 응답이 반환됩니다 """) @GetMapping("/lost-item/v2") ResponseEntity getLostItemArticlesV2( + @Parameter(description = "분실물 타입 (LOST: 분실물, FOUND: 습득물)") @RequestParam(required = false) String type, @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer limit, + @RequestParam(required = false, name = "category", defaultValue = "ALL") LostItemCategoryFilter itemCategory, + @Parameter(description = "물품 상태 (ALL: 전체, FOUND: 찾음, NOT_FOUND: 찾는 중)") @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, + @Parameter(description = "정렬 순서 (LATEST: 최신순(default), OLDEST: 오래된순)") + @RequestParam(required = false, defaultValue = "LATEST") LostItemSortType sort, + @Parameter(description = "내 게시물 (ALL: 전체, MY: 내 게시물)") + @RequestParam(required = false, name = "author", defaultValue = "ALL") LostItemAuthorFilter authorType, @UserId Integer userId ); - @ApiResponses( value = { @ApiResponse(responseCode = "200"), diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java index ee3792613..ae5e965f3 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java @@ -17,12 +17,16 @@ import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; -import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemSortType; import in.koreatech.koin.domain.community.article.service.LostItemArticleService; import in.koreatech.koin.domain.community.article.service.LostItemFoundService; import in.koreatech.koin.global.auth.Auth; import in.koreatech.koin.global.auth.UserId; import in.koreatech.koin.global.ipaddress.IpAddress; +import io.swagger.v3.oas.annotations.Parameter; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -60,13 +64,17 @@ public ResponseEntity getLostItemArticles( @GetMapping("/lost-item/v2") public ResponseEntity getLostItemArticlesV2( - @RequestParam(required = false) String type, + @Parameter(description = "분실물 타입 (LOST: 분실물, FOUND: 습득물)") @RequestParam(required = false) String type, @RequestParam(required = false) Integer page, @RequestParam(required = false) Integer limit, + @RequestParam(required = false, name = "category", defaultValue = "ALL") LostItemCategoryFilter itemCategory, @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, + @RequestParam(required = false, name = "sort", defaultValue = "LATEST") LostItemSortType sort, + @RequestParam(required = false, name = "author", defaultValue = "ALL") LostItemAuthorFilter authorType, @UserId Integer userId ) { - LostItemArticlesResponse response = lostItemArticleService.getLostItemArticlesV2(type, page, limit, userId, foundStatus); + LostItemArticlesResponse response = lostItemArticleService.getLostItemArticlesV2(type, page, limit, userId, + foundStatus, itemCategory, sort, authorType); return ResponseEntity.ok().body(response); } @@ -83,7 +91,8 @@ public ResponseEntity createLostItemArticle( @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer studentId, @RequestBody @Valid LostItemArticlesRequest lostItemArticlesRequest ) { - LostItemArticleResponse response = lostItemArticleService.createLostItemArticle(studentId, lostItemArticlesRequest); + LostItemArticleResponse response = lostItemArticleService.createLostItemArticle(studentId, + lostItemArticlesRequest); return ResponseEntity.status(HttpStatus.CREATED).body(response); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemAuthorFilter.java b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemAuthorFilter.java new file mode 100644 index 000000000..172cb673b --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemAuthorFilter.java @@ -0,0 +1,29 @@ +package in.koreatech.koin.domain.community.article.model.filter; + +import in.koreatech.koin.global.code.ApiResponseCode; +import in.koreatech.koin.global.exception.CustomException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum LostItemAuthorFilter { + + ALL { + @Override + public Integer getRequiredAuthorId(Integer userId) { + return null; + } + }, + MY { + @Override + public Integer getRequiredAuthorId(Integer userId) { + if (userId == null) { + throw CustomException.of(ApiResponseCode.UNAUTHORIZED_USER); + } + return userId; + } + }; + + public abstract Integer getRequiredAuthorId(Integer userId); +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemCategoryFilter.java b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemCategoryFilter.java new file mode 100644 index 000000000..2757fdaee --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemCategoryFilter.java @@ -0,0 +1,18 @@ +package in.koreatech.koin.domain.community.article.model.filter; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum LostItemCategoryFilter { + + ALL(null), + CARD("카드"), + ID("신분증"), + WALLET("지갑"), + ELECTRONICS("전자제품"), + ETC("기타"); + + private String status; +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/model/LostItemFoundStatus.java b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemFoundStatus.java similarity index 79% rename from src/main/java/in/koreatech/koin/domain/community/article/model/LostItemFoundStatus.java rename to src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemFoundStatus.java index 5488eaa06..85cb876aa 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/model/LostItemFoundStatus.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemFoundStatus.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.domain.community.article.model; +package in.koreatech.koin.domain.community.article.model.filter; public enum LostItemFoundStatus { diff --git a/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemSortType.java b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemSortType.java new file mode 100644 index 000000000..e22eeefcd --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/model/filter/LostItemSortType.java @@ -0,0 +1,13 @@ +package in.koreatech.koin.domain.community.article.model.filter; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum LostItemSortType { + LATEST("LATEST"), + OLDEST("OLDEST"); + + private final String value; +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java index 05973b594..c78d71b1e 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java @@ -6,12 +6,15 @@ import in.koreatech.koin.domain.community.article.dto.LostItemArticleSummary; import in.koreatech.koin.domain.community.article.model.Article; +import in.koreatech.koin.domain.community.article.model.filter.LostItemSortType; public interface LostItemArticleCustomRepository { LostItemArticleSummary getArticleSummary(Integer articleId); - Long countLostItemArticlesWithFilters(String type, Boolean isFound, Integer lostItemArticleBoardId); + Long countLostItemArticlesWithFilters(String type, Boolean isFound, String itemCategory, + Integer lostItemArticleBoardId, Integer authorId); - List
findLostItemArticlesWithFilters(Integer boardId, String type, Boolean isFound, PageRequest pageRequest); + List
findLostItemArticlesWithFilters(Integer boardId, String type, Boolean isFound, + String itemCategoryFilter, LostItemSortType sort, PageRequest pageRequest, Integer authorId); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java index a93a08965..4436a8067 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java @@ -6,17 +6,17 @@ import java.util.List; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Repository; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import in.koreatech.koin.domain.community.article.dto.LostItemArticleSummary; import in.koreatech.koin.domain.community.article.model.Article; +import in.koreatech.koin.domain.community.article.model.filter.LostItemSortType; import lombok.RequiredArgsConstructor; @Repository @@ -38,8 +38,9 @@ public LostItemArticleSummary getArticleSummary(Integer articleId) { .fetchFirst(); } - public Long countLostItemArticlesWithFilters(String type, Boolean isFound, Integer lostItemArticleBoardId) { - BooleanExpression filter = getFilter(lostItemArticleBoardId, type, isFound); + public Long countLostItemArticlesWithFilters(String type, Boolean isFound, String itemCategory, + Integer lostItemArticleBoardId, Integer authorId) { + BooleanExpression filter = getFilter(lostItemArticleBoardId, type, itemCategory, isFound, authorId); return queryFactory .select(article.count()) @@ -49,23 +50,24 @@ public Long countLostItemArticlesWithFilters(String type, Boolean isFound, Integ .fetchOne(); } - public List
findLostItemArticlesWithFilters( - Integer boardId, String type, Boolean isFound, PageRequest pageRequest) { + public List
findLostItemArticlesWithFilters(Integer boardId, String type, Boolean isFound, + String itemCategory, LostItemSortType sort, PageRequest pageRequest, Integer authorId) { - BooleanExpression predicate = getFilter(boardId, type, isFound); + BooleanExpression predicate = getFilter(boardId, type, itemCategory, isFound, authorId); + OrderSpecifier[] orderSpecifiers = getOrderSpecifiers(sort); return queryFactory .selectFrom(article) .leftJoin(article.lostItemArticle, lostItemArticle).fetchJoin() .leftJoin(lostItemArticle.author).fetchJoin() .where(predicate) - .orderBy(article.createdAt.desc(), article.id.desc()) + .orderBy(orderSpecifiers) .offset(pageRequest.getOffset()) .limit(pageRequest.getPageSize()) .fetch(); } - private BooleanExpression getFilter(Integer boardId, String type, Boolean isFound) { + private BooleanExpression getFilter(Integer boardId, String type, String itemCategory, Boolean isFound, Integer authorId) { BooleanExpression filter = article.board.id.eq(boardId) .and(article.isDeleted.isFalse()) .and(article.lostItemArticle.isNotNull()); @@ -78,6 +80,28 @@ private BooleanExpression getFilter(Integer boardId, String type, Boolean isFoun filter = filter.and(lostItemArticle.isFound.eq(isFound)); } + if (itemCategory != null && !itemCategory.isBlank()) { + filter = filter.and(lostItemArticle.category.eq(itemCategory)); + } + + if (authorId != null) { + filter = filter.and(lostItemArticle.author.id.eq(authorId)); + } + return filter; } + + private OrderSpecifier[] getOrderSpecifiers(LostItemSortType sort) { + if (sort == LostItemSortType.OLDEST) { + return new OrderSpecifier[] { + article.createdAt.asc(), + article.id.asc() + }; + } + + return new OrderSpecifier[] { + article.createdAt.desc(), + article.id.desc() + }; + } } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java index e4d82e57b..71b1116c1 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/ArticleService.java @@ -6,12 +6,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -21,26 +18,16 @@ import in.koreatech.koin.domain.community.article.dto.ArticleResponse; import in.koreatech.koin.domain.community.article.dto.ArticlesResponse; import in.koreatech.koin.domain.community.article.dto.HotArticleItemResponse; -import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; -import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; -import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; import in.koreatech.koin.domain.community.article.exception.ArticleBoardMisMatchException; import in.koreatech.koin.domain.community.article.model.Article; import in.koreatech.koin.domain.community.article.model.Board; -import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; import in.koreatech.koin.domain.community.article.model.redis.ArticleHitUser; import in.koreatech.koin.domain.community.article.model.redis.PopularKeywordTracker; import in.koreatech.koin.domain.community.article.model.KeywordRankingManager; import in.koreatech.koin.domain.community.article.repository.ArticleRepository; import in.koreatech.koin.domain.community.article.repository.BoardRepository; -import in.koreatech.koin.domain.community.article.repository.LostItemArticleRepository; import in.koreatech.koin.domain.community.article.repository.redis.ArticleHitUserRepository; import in.koreatech.koin.domain.community.article.repository.redis.HotArticleRepository; -import in.koreatech.koin.common.event.ArticleKeywordEvent; -import in.koreatech.koin.domain.community.util.KeywordExtractor; -import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.repository.UserRepository; -import in.koreatech.koin.global.auth.exception.AuthorizationException; import in.koreatech.koin.global.exception.custom.KoinIllegalArgumentException; import in.koreatech.koin.common.model.Criteria; import in.koreatech.koin.infrastructure.s3.client.S3Client; diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java index c9296a6ab..adec47c95 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java @@ -21,7 +21,10 @@ import in.koreatech.koin.domain.community.article.exception.ArticleBoardMisMatchException; import in.koreatech.koin.domain.community.article.model.Article; import in.koreatech.koin.domain.community.article.model.Board; -import in.koreatech.koin.domain.community.article.model.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; +import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemSortType; import in.koreatech.koin.domain.community.article.model.redis.PopularKeywordTracker; import in.koreatech.koin.domain.community.article.repository.ArticleRepository; import in.koreatech.koin.domain.community.article.repository.BoardRepository; @@ -91,19 +94,27 @@ public LostItemArticlesResponse getLostItemArticles(String type, Integer page, I } public LostItemArticlesResponse getLostItemArticlesV2(String type, Integer page, Integer limit, Integer userId, - LostItemFoundStatus foundStatus) { + LostItemFoundStatus foundStatus, LostItemCategoryFilter itemCategory, LostItemSortType sort, + LostItemAuthorFilter authorType) { + Integer authorIdFilter = authorType.getRequiredAuthorId(userId); + Boolean foundStatusFilter = Optional.ofNullable(foundStatus) .map(LostItemFoundStatus::getQueryStatus) .orElse(null); + String itemCategoryFilter = Optional.ofNullable(itemCategory) + .filter(category -> category != LostItemCategoryFilter.ALL) + .map(LostItemCategoryFilter::getStatus) + .orElse(null); + Long total = lostItemArticleRepository.countLostItemArticlesWithFilters(type, foundStatusFilter, - LOST_ITEM_BOARD_ID); + itemCategoryFilter, LOST_ITEM_BOARD_ID, authorIdFilter); Criteria criteria = Criteria.of(page, limit, total.intValue()); - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), ARTICLES_SORT); + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); List
articles = lostItemArticleRepository.findLostItemArticlesWithFilters(LOST_ITEM_BOARD_ID, type, - foundStatusFilter, pageRequest); + foundStatusFilter, itemCategoryFilter, sort, pageRequest, authorIdFilter); Page
articlePage = new PageImpl<>(articles, pageRequest, total); return LostItemArticlesResponse.of(articlePage, criteria, userId); diff --git a/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java b/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java index 46fb02300..207dc2476 100644 --- a/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java +++ b/src/main/java/in/koreatech/koin/global/code/ApiResponseCode.java @@ -88,6 +88,7 @@ public enum ApiResponseCode { * 401 Unauthorized (인증 필요) */ WITHDRAWN_USER(HttpStatus.UNAUTHORIZED, "탈퇴한 계정입니다."), + UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), /** * 403 Forbidden (인가 필요) From eb9a57fff0379cfbdc0110019b945544bf23ad8f Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Wed, 14 Jan 2026 01:53:54 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EC=B0=BE=EB=8A=94=EC=A4=91=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=9D=B8=20=EB=B6=84=EC=8B=A4=EB=AC=BC=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EA=B0=9C=EC=88=98=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20GET=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/LostItemArticleApi.java | 8 ++++++++ .../controller/LostItemArticleController.java | 7 +++++++ .../NotFoundLostItemArticleCountResponse.java | 16 ++++++++++++++++ .../repository/LostItemArticleRepository.java | 8 +++++++- .../article/service/LostItemFoundService.java | 7 +++++++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java index 715894f36..931de378d 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -18,6 +18,7 @@ import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; +import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; @@ -157,4 +158,11 @@ ResponseEntity markLostItemArticleAsFound( @Operation(summary = "주인 찾음 상태인 분실물 게시글 총 개수 조회") @GetMapping("/lost-item/found/count") ResponseEntity getFoundLostItemArticlesCount(); + + @ApiResponseCodes({ + OK + }) + @Operation(summary = "주인 찾고 있음 상태인 분실물 게시글 총 개수 조회") + @GetMapping("/lost-item/notfound/count") + ResponseEntity getNotFoundLostItemArticlesCount(); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java index ae5e965f3..ab34cc6a0 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java @@ -17,6 +17,7 @@ import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; +import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; @@ -119,4 +120,10 @@ public ResponseEntity getFoundLostItemArticle FoundLostItemArticleCountResponse response = lostItemFoundService.countFoundArticles(); return ResponseEntity.ok().body(response); } + + @GetMapping("/lost-item/notfound/count") + public ResponseEntity getNotFoundLostItemArticlesCount() { + NotFoundLostItemArticleCountResponse response = lostItemFoundService.countNotFoundArticles(); + return ResponseEntity.ok().body(response); + } } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java b/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java new file mode 100644 index 000000000..14f0bfcf1 --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java @@ -0,0 +1,16 @@ +package in.koreatech.koin.domain.community.article.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import io.swagger.v3.oas.annotations.media.Schema; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +public record NotFoundLostItemArticleCountResponse( + + @Schema(description = "찾음 상태의 분실물 게시글 개수", example = "13", requiredMode = REQUIRED) + Integer notFoundCount +) { +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleRepository.java b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleRepository.java index 2b42158c8..5bf6bbb5b 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleRepository.java @@ -23,8 +23,14 @@ default LostItemArticle getByArticleId(Integer articleId) { } @Query( - value = "SELECT count(*) FROM lost_item_articles WHERE is_found = 1 AND is_deleted = 0", + value = "SELECT count(*) FROM lost_item_articles WHERE is_found = 1 AND is_deleted = 0 AND author_id is not null", nativeQuery = true ) Integer getFoundLostItemArticleCount(); + + @Query( + value = "SELECT count(*) FROM lost_item_articles WHERE is_found = 0 AND is_deleted = 0 AND author_id is not null", + nativeQuery = true + ) + Integer getNotFoundLostItemArticleCount(); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java index d2fc8cc6b..baa07dc09 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java @@ -4,6 +4,7 @@ import org.springframework.transaction.annotation.Transactional; import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; +import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.model.LostItemArticle; import in.koreatech.koin.domain.community.article.repository.ArticleRepository; import in.koreatech.koin.domain.community.article.repository.LostItemArticleRepository; @@ -28,4 +29,10 @@ public FoundLostItemArticleCountResponse countFoundArticles() { Integer foundCount = lostItemArticleRepository.getFoundLostItemArticleCount(); return new FoundLostItemArticleCountResponse(foundCount); } + + @Transactional(readOnly = true) + public NotFoundLostItemArticleCountResponse countNotFoundArticles() { + Integer foundCount = lostItemArticleRepository.getNotFoundLostItemArticleCount(); + return new NotFoundLostItemArticleCountResponse(foundCount); + } } From b163bca414d1d3b39b99b250520f47a5cabc4f07 Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Wed, 14 Jan 2026 09:30:10 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20API=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/LostItemArticleApi.java | 18 ++++----- .../controller/LostItemArticleController.java | 21 +++------- .../LostItemArticleStatisticsResponse.java | 19 ++++++++++ .../NotFoundLostItemArticleCountResponse.java | 2 +- .../service/LostItemArticleService.java | 16 ++++++++ .../article/service/LostItemFoundService.java | 38 ------------------- 6 files changed, 50 insertions(+), 64 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/community/article/dto/LostItemArticleStatisticsResponse.java delete mode 100644 src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java index 931de378d..6d7da9498 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -16,6 +16,7 @@ import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticleStatisticsResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; @@ -155,14 +156,11 @@ ResponseEntity markLostItemArticleAsFound( @ApiResponseCodes({ OK }) - @Operation(summary = "주인 찾음 상태인 분실물 게시글 총 개수 조회") - @GetMapping("/lost-item/found/count") - ResponseEntity getFoundLostItemArticlesCount(); - - @ApiResponseCodes({ - OK - }) - @Operation(summary = "주인 찾고 있음 상태인 분실물 게시글 총 개수 조회") - @GetMapping("/lost-item/notfound/count") - ResponseEntity getNotFoundLostItemArticlesCount(); + @Operation(summary = "분실물 게시글 통계 조회", description = """ + ### 분실물 게시글 통계 조회 + - found_count : 주인 찾음 상태의 게시글 개수 + - not_found_count : 주인 찾는 중 상태의 게시글 개수 + """) + @GetMapping("/lost-item/stats") + ResponseEntity getLostItemArticlesStats(); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java index ab34cc6a0..1d0d96c42 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java @@ -13,17 +13,15 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticleStatisticsResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; -import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; -import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; +import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; import in.koreatech.koin.domain.community.article.model.filter.LostItemSortType; import in.koreatech.koin.domain.community.article.service.LostItemArticleService; -import in.koreatech.koin.domain.community.article.service.LostItemFoundService; import in.koreatech.koin.global.auth.Auth; import in.koreatech.koin.global.auth.UserId; import in.koreatech.koin.global.ipaddress.IpAddress; @@ -37,7 +35,6 @@ public class LostItemArticleController implements LostItemArticleApi { private final LostItemArticleService lostItemArticleService; - private final LostItemFoundService lostItemFoundService; @GetMapping("/lost-item/search") public ResponseEntity searchArticles( @@ -111,19 +108,13 @@ public ResponseEntity markLostItemArticleAsFound( @PathVariable("id") Integer articleId, @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId ) { - lostItemFoundService.markAsFound(userId, articleId); + lostItemArticleService.markLostItemArticleAsFound(userId, articleId); return ResponseEntity.noContent().build(); } - @GetMapping("/lost-item/found/count") - public ResponseEntity getFoundLostItemArticlesCount() { - FoundLostItemArticleCountResponse response = lostItemFoundService.countFoundArticles(); - return ResponseEntity.ok().body(response); - } - - @GetMapping("/lost-item/notfound/count") - public ResponseEntity getNotFoundLostItemArticlesCount() { - NotFoundLostItemArticleCountResponse response = lostItemFoundService.countNotFoundArticles(); + @GetMapping("/lost-item/stats") + public ResponseEntity getLostItemArticlesStats() { + LostItemArticleStatisticsResponse response = lostItemArticleService.getLostItemArticlesStats(); return ResponseEntity.ok().body(response); } } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/dto/LostItemArticleStatisticsResponse.java b/src/main/java/in/koreatech/koin/domain/community/article/dto/LostItemArticleStatisticsResponse.java new file mode 100644 index 000000000..90cbdcf8f --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/community/article/dto/LostItemArticleStatisticsResponse.java @@ -0,0 +1,19 @@ +package in.koreatech.koin.domain.community.article.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import io.swagger.v3.oas.annotations.media.Schema; + +@JsonNaming(SnakeCaseStrategy.class) +public record LostItemArticleStatisticsResponse( + + @Schema(description = "찾음 상태의 분실물 게시글 개수", example = "13", requiredMode = REQUIRED) + Integer foundCount, + + @Schema(description = "찾는 중 상태의 분실물 게시글 개수", example = "36", requiredMode = REQUIRED) + Integer notFoundCount +) { +} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java b/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java index 14f0bfcf1..6ef069b7c 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java @@ -10,7 +10,7 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public record NotFoundLostItemArticleCountResponse( - @Schema(description = "찾음 상태의 분실물 게시글 개수", example = "13", requiredMode = REQUIRED) + @Schema(description = "찾는 중 상태의 분실물 게시글 개수", example = "13", requiredMode = REQUIRED) Integer notFoundCount ) { } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java index adec47c95..5ad18f95e 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java @@ -16,11 +16,13 @@ import in.koreatech.koin.common.event.ArticleKeywordEvent; import in.koreatech.koin.common.model.Criteria; import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; +import in.koreatech.koin.domain.community.article.dto.LostItemArticleStatisticsResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; import in.koreatech.koin.domain.community.article.exception.ArticleBoardMisMatchException; import in.koreatech.koin.domain.community.article.model.Article; import in.koreatech.koin.domain.community.article.model.Board; +import in.koreatech.koin.domain.community.article.model.LostItemArticle; import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; @@ -159,6 +161,20 @@ public void deleteLostItemArticle(Integer articleId, Integer userId) { foundArticle.delete(); } + @Transactional(readOnly = true) + public LostItemArticleStatisticsResponse getLostItemArticlesStats() { + Integer foundCount = lostItemArticleRepository.getFoundLostItemArticleCount(); + Integer notFoundCount = lostItemArticleRepository.getNotFoundLostItemArticleCount(); + return new LostItemArticleStatisticsResponse(foundCount, notFoundCount); + } + + @Transactional + public void markLostItemArticleAsFound(Integer userId, Integer articleId) { + LostItemArticle lostItemArticle = articleRepository.getById(articleId).getLostItemArticle(); + lostItemArticle.checkOwnership(userId); + lostItemArticle.markAsFound(); + } + private void setPrevNextArticle(Integer boardId, Article article) { Article prevArticle; Article nextArticle; diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java deleted file mode 100644 index baa07dc09..000000000 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemFoundService.java +++ /dev/null @@ -1,38 +0,0 @@ -package in.koreatech.koin.domain.community.article.service; - -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; -import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; -import in.koreatech.koin.domain.community.article.model.LostItemArticle; -import in.koreatech.koin.domain.community.article.repository.ArticleRepository; -import in.koreatech.koin.domain.community.article.repository.LostItemArticleRepository; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class LostItemFoundService { - - private final ArticleRepository articleRepository; - private final LostItemArticleRepository lostItemArticleRepository; - - @Transactional - public void markAsFound(Integer userId, Integer articleId) { - LostItemArticle lostItemArticle = articleRepository.getById(articleId).getLostItemArticle(); - lostItemArticle.checkOwnership(userId); - lostItemArticle.markAsFound(); - } - - @Transactional(readOnly = true) - public FoundLostItemArticleCountResponse countFoundArticles() { - Integer foundCount = lostItemArticleRepository.getFoundLostItemArticleCount(); - return new FoundLostItemArticleCountResponse(foundCount); - } - - @Transactional(readOnly = true) - public NotFoundLostItemArticleCountResponse countNotFoundArticles() { - Integer foundCount = lostItemArticleRepository.getNotFoundLostItemArticleCount(); - return new NotFoundLostItemArticleCountResponse(foundCount); - } -} From e6ed716152f00f88b127dad9d16b27af18def90b Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Wed, 14 Jan 2026 09:33:01 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20dto?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/LostItemArticleApi.java | 3 --- .../dto/FoundLostItemArticleCountResponse.java | 17 ----------------- .../NotFoundLostItemArticleCountResponse.java | 16 ---------------- 3 files changed, 36 deletions(-) delete mode 100644 src/main/java/in/koreatech/koin/domain/community/article/dto/FoundLostItemArticleCountResponse.java delete mode 100644 src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java index 6d7da9498..5dd86686c 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -2,7 +2,6 @@ import static in.koreatech.koin.domain.user.model.UserType.*; import static in.koreatech.koin.global.code.ApiResponseCode.*; -import static in.koreatech.koin.global.code.ApiResponseCode.OK; import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH; import org.springframework.http.ResponseEntity; @@ -14,12 +13,10 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import in.koreatech.koin.domain.community.article.dto.FoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticleResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticleStatisticsResponse; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesRequest; import in.koreatech.koin.domain.community.article.dto.LostItemArticlesResponse; -import in.koreatech.koin.domain.community.article.dto.NotFoundLostItemArticleCountResponse; import in.koreatech.koin.domain.community.article.model.filter.LostItemAuthorFilter; import in.koreatech.koin.domain.community.article.model.filter.LostItemCategoryFilter; import in.koreatech.koin.domain.community.article.model.filter.LostItemFoundStatus; diff --git a/src/main/java/in/koreatech/koin/domain/community/article/dto/FoundLostItemArticleCountResponse.java b/src/main/java/in/koreatech/koin/domain/community/article/dto/FoundLostItemArticleCountResponse.java deleted file mode 100644 index 45af929d2..000000000 --- a/src/main/java/in/koreatech/koin/domain/community/article/dto/FoundLostItemArticleCountResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package in.koreatech.koin.domain.community.article.dto; - -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; - -import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -import io.swagger.v3.oas.annotations.media.Schema; - -@JsonNaming(SnakeCaseStrategy.class) -public record FoundLostItemArticleCountResponse( - - @Schema(description = "찾음 상태의 분실물 게시글 개수", example = "13", requiredMode = REQUIRED) - Integer foundCount -) { - -} diff --git a/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java b/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java deleted file mode 100644 index 6ef069b7c..000000000 --- a/src/main/java/in/koreatech/koin/domain/community/article/dto/NotFoundLostItemArticleCountResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package in.koreatech.koin.domain.community.article.dto; - -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; - -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonNaming; - -import io.swagger.v3.oas.annotations.media.Schema; - -@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public record NotFoundLostItemArticleCountResponse( - - @Schema(description = "찾는 중 상태의 분실물 게시글 개수", example = "13", requiredMode = REQUIRED) - Integer notFoundCount -) { -} From 4b94cf36b2884de45d8c0e58066792ae9cd51e09 Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Wed, 14 Jan 2026 20:31:37 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20title=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/LostItemArticleApi.java | 2 ++ .../controller/LostItemArticleController.java | 3 ++- .../LostItemArticleCustomRepository.java | 4 ++-- .../LostItemArticleCustomRepositoryImpl.java | 15 ++++++++++----- .../article/service/LostItemArticleService.java | 10 +++++++--- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java index 5dd86686c..ae7768653 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -90,6 +90,8 @@ ResponseEntity getLostItemArticlesV2( @RequestParam(required = false, defaultValue = "LATEST") LostItemSortType sort, @Parameter(description = "내 게시물 (ALL: 전체, MY: 내 게시물)") @RequestParam(required = false, name = "author", defaultValue = "ALL") LostItemAuthorFilter authorType, + @Parameter(description = "게시글 제목") + @RequestParam(required = false) String query, @UserId Integer userId ); diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java index 1d0d96c42..290625b7b 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java @@ -69,10 +69,11 @@ public ResponseEntity getLostItemArticlesV2( @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, @RequestParam(required = false, name = "sort", defaultValue = "LATEST") LostItemSortType sort, @RequestParam(required = false, name = "author", defaultValue = "ALL") LostItemAuthorFilter authorType, + @RequestParam(required = false, name = "title") String query, @UserId Integer userId ) { LostItemArticlesResponse response = lostItemArticleService.getLostItemArticlesV2(type, page, limit, userId, - foundStatus, itemCategory, sort, authorType); + foundStatus, itemCategory, sort, authorType, query); return ResponseEntity.ok().body(response); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java index c78d71b1e..e7360b4f1 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepository.java @@ -13,8 +13,8 @@ public interface LostItemArticleCustomRepository { LostItemArticleSummary getArticleSummary(Integer articleId); Long countLostItemArticlesWithFilters(String type, Boolean isFound, String itemCategory, - Integer lostItemArticleBoardId, Integer authorId); + Integer lostItemArticleBoardId, Integer authorId, String titleQuery); List
findLostItemArticlesWithFilters(Integer boardId, String type, Boolean isFound, - String itemCategoryFilter, LostItemSortType sort, PageRequest pageRequest, Integer authorId); + String itemCategoryFilter, LostItemSortType sort, PageRequest pageRequest, Integer authorId, String titleQuery); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java index 4436a8067..19d8b0a16 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/repository/LostItemArticleCustomRepositoryImpl.java @@ -39,8 +39,8 @@ public LostItemArticleSummary getArticleSummary(Integer articleId) { } public Long countLostItemArticlesWithFilters(String type, Boolean isFound, String itemCategory, - Integer lostItemArticleBoardId, Integer authorId) { - BooleanExpression filter = getFilter(lostItemArticleBoardId, type, itemCategory, isFound, authorId); + Integer lostItemArticleBoardId, Integer authorId, String titleQuery) { + BooleanExpression filter = getFilter(lostItemArticleBoardId, type, itemCategory, isFound, authorId, titleQuery); return queryFactory .select(article.count()) @@ -51,9 +51,9 @@ public Long countLostItemArticlesWithFilters(String type, Boolean isFound, Strin } public List
findLostItemArticlesWithFilters(Integer boardId, String type, Boolean isFound, - String itemCategory, LostItemSortType sort, PageRequest pageRequest, Integer authorId) { + String itemCategory, LostItemSortType sort, PageRequest pageRequest, Integer authorId, String titleQuery) { - BooleanExpression predicate = getFilter(boardId, type, itemCategory, isFound, authorId); + BooleanExpression predicate = getFilter(boardId, type, itemCategory, isFound, authorId, titleQuery); OrderSpecifier[] orderSpecifiers = getOrderSpecifiers(sort); return queryFactory @@ -67,7 +67,8 @@ public List
findLostItemArticlesWithFilters(Integer boardId, String typ .fetch(); } - private BooleanExpression getFilter(Integer boardId, String type, String itemCategory, Boolean isFound, Integer authorId) { + private BooleanExpression getFilter(Integer boardId, String type, String itemCategory, Boolean isFound, + Integer authorId, String titleQuery) { BooleanExpression filter = article.board.id.eq(boardId) .and(article.isDeleted.isFalse()) .and(article.lostItemArticle.isNotNull()); @@ -88,6 +89,10 @@ private BooleanExpression getFilter(Integer boardId, String type, String itemCat filter = filter.and(lostItemArticle.author.id.eq(authorId)); } + if (titleQuery != null && !titleQuery.isBlank()) { + filter = filter.and(article.title.containsIgnoreCase(titleQuery)); + } + return filter; } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java index 5ad18f95e..206058290 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java @@ -97,9 +97,13 @@ public LostItemArticlesResponse getLostItemArticles(String type, Integer page, I public LostItemArticlesResponse getLostItemArticlesV2(String type, Integer page, Integer limit, Integer userId, LostItemFoundStatus foundStatus, LostItemCategoryFilter itemCategory, LostItemSortType sort, - LostItemAuthorFilter authorType) { + LostItemAuthorFilter authorType, String titleQuery) { Integer authorIdFilter = authorType.getRequiredAuthorId(userId); + String refinedTitleQuery = Optional.ofNullable(titleQuery) + .map(String::trim) + .orElse(null); + Boolean foundStatusFilter = Optional.ofNullable(foundStatus) .map(LostItemFoundStatus::getQueryStatus) .orElse(null); @@ -110,13 +114,13 @@ public LostItemArticlesResponse getLostItemArticlesV2(String type, Integer page, .orElse(null); Long total = lostItemArticleRepository.countLostItemArticlesWithFilters(type, foundStatusFilter, - itemCategoryFilter, LOST_ITEM_BOARD_ID, authorIdFilter); + itemCategoryFilter, LOST_ITEM_BOARD_ID, authorIdFilter, refinedTitleQuery); Criteria criteria = Criteria.of(page, limit, total.intValue()); PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); List
articles = lostItemArticleRepository.findLostItemArticlesWithFilters(LOST_ITEM_BOARD_ID, type, - foundStatusFilter, itemCategoryFilter, sort, pageRequest, authorIdFilter); + foundStatusFilter, itemCategoryFilter, sort, pageRequest, authorIdFilter, refinedTitleQuery); Page
articlePage = new PageImpl<>(articles, pageRequest, total); return LostItemArticlesResponse.of(articlePage, criteria, userId); From 16816e838dda5642430854939d5dd5044137666d Mon Sep 17 00:00:00 2001 From: DHkimgit Date: Fri, 16 Jan 2026 12:58:10 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/article/controller/LostItemArticleApi.java | 6 +++--- .../article/controller/LostItemArticleController.java | 4 ++-- .../community/article/service/LostItemArticleService.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java index ae7768653..ef569c838 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleApi.java @@ -91,7 +91,7 @@ ResponseEntity getLostItemArticlesV2( @Parameter(description = "내 게시물 (ALL: 전체, MY: 내 게시물)") @RequestParam(required = false, name = "author", defaultValue = "ALL") LostItemAuthorFilter authorType, @Parameter(description = "게시글 제목") - @RequestParam(required = false) String query, + @RequestParam(required = false) String title, @UserId Integer userId ); @@ -120,7 +120,7 @@ ResponseEntity getLostItemArticle( @Operation(summary = "분실물 게시글 등록") @PostMapping("/lost-item") ResponseEntity createLostItemArticle( - @Auth(permit = {STUDENT, COUNCIL}) Integer userId, + @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer userId, @RequestBody @Valid LostItemArticlesRequest lostItemArticlesRequest ); @@ -137,7 +137,7 @@ ResponseEntity createLostItemArticle( @DeleteMapping("/lost-item/{id}") ResponseEntity deleteLostItemArticle( @PathVariable("id") Integer articleId, - @Auth(permit = {STUDENT, COUNCIL}) Integer councilId + @Auth(permit = {GENERAL, STUDENT, COUNCIL}) Integer councilId ); @ApiResponseCodes({ diff --git a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java index 290625b7b..e044cc1de 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/controller/LostItemArticleController.java @@ -69,11 +69,11 @@ public ResponseEntity getLostItemArticlesV2( @RequestParam(required = false, defaultValue = "ALL") LostItemFoundStatus foundStatus, @RequestParam(required = false, name = "sort", defaultValue = "LATEST") LostItemSortType sort, @RequestParam(required = false, name = "author", defaultValue = "ALL") LostItemAuthorFilter authorType, - @RequestParam(required = false, name = "title") String query, + @RequestParam(required = false, name = "title") String title, @UserId Integer userId ) { LostItemArticlesResponse response = lostItemArticleService.getLostItemArticlesV2(type, page, limit, userId, - foundStatus, itemCategory, sort, authorType, query); + foundStatus, itemCategory, sort, authorType, title); return ResponseEntity.ok().body(response); } diff --git a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java index 206058290..1b3135641 100644 --- a/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java +++ b/src/main/java/in/koreatech/koin/domain/community/article/service/LostItemArticleService.java @@ -40,6 +40,7 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class LostItemArticleService { public static final int LOST_ITEM_BOARD_ID = 14; @@ -165,7 +166,6 @@ public void deleteLostItemArticle(Integer articleId, Integer userId) { foundArticle.delete(); } - @Transactional(readOnly = true) public LostItemArticleStatisticsResponse getLostItemArticlesStats() { Integer foundCount = lostItemArticleRepository.getFoundLostItemArticleCount(); Integer notFoundCount = lostItemArticleRepository.getNotFoundLostItemArticleCount();