-
Notifications
You must be signed in to change notification settings - Fork 0
[DEV-265/BE] feat: 회사를 검색하는 API 개발 #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
The head ref may contain hidden characters: "DEV-265/feat/\uD68C\uC0AC\uB97C-\uAC80\uC0C9\uD558\uB294-API-\uAC1C\uBC1C"
Changes from 8 commits
2136798
c280250
6b2ce53
4499164
8c0848e
5900c35
30baa77
8d911fe
2302f5e
00cd6c0
006e726
8fe8f8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||
| package com.shyashyashya.refit.domain.company.api; | ||||||||||
|
|
||||||||||
| import static com.shyashyashya.refit.global.model.ResponseCode.COMMON200; | ||||||||||
|
|
||||||||||
| import com.shyashyashya.refit.domain.company.api.response.CompanyResponse; | ||||||||||
| import com.shyashyashya.refit.domain.company.service.CompanyService; | ||||||||||
| import com.shyashyashya.refit.global.dto.ApiResponse; | ||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||
| import org.springdoc.core.annotations.ParameterObject; | ||||||||||
| import org.springframework.data.domain.Page; | ||||||||||
| import org.springframework.data.domain.Pageable; | ||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||||||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||||||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||||||||
| import org.springframework.web.bind.annotation.RestController; | ||||||||||
|
|
||||||||||
| @RestController | ||||||||||
| @RequiredArgsConstructor | ||||||||||
| @RequestMapping("/company") | ||||||||||
| public class CompanyController { | ||||||||||
|
|
||||||||||
| private final CompanyService companyService; | ||||||||||
|
|
||||||||||
| @GetMapping | ||||||||||
| public ResponseEntity<ApiResponse<Page<CompanyResponse>>> findCompanies( | ||||||||||
| @RequestParam(required = false) String q, @ParameterObject Pageable pageable) { | ||||||||||
|
Comment on lines
+30
to
+31
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 변수명 q 보다 구체적으로 적어주시면 가독성이 더 좋을 것 같습니다!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. api 사용시
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. query의 q인가요?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 실제 path 뒤에 붙는 쿼리 스트링에서도 keyword, query 와 같이 구체화되면 좋을 것 같습니다! query 라는 글자가 너무 길지도 않은 것 같고, 축약해서 사용했을 때 더 좋은 이유가 잘 생각이 안나는 것 같아요! |
||||||||||
| var response = companyService.findCompanies(q, pageable); | ||||||||||
|
Comment on lines
+31
to
+32
|
||||||||||
| @RequestParam(required = false) String q, @ParameterObject Pageable pageable) { | |
| var response = companyService.findCompanies(q, pageable); | |
| @RequestParam(name = "query", required = false) String query, @ParameterObject Pageable pageable) { | |
| var response = companyService.findCompanies(query, pageable); |
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a significant discrepancy between the PR title/description and the mentioned issue #265. The PR is about implementing a company search API (DEV-265/BE), but the mentioned issue #265 is about "스크랩 폴더 목록 조회하는 API 구현" (DEV-191/BE - retrieving scrap folder lists with QnA set inclusion status). This appears to be a mismatch in the issue reference. Please verify that the correct issue is linked to this PR.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,3 @@ | ||||||
| package com.shyashyashya.refit.domain.company.api.response; | ||||||
|
|
||||||
| public record CompanyResponse(Long companyId, String companyName) {} | ||||||
|
||||||
| public record CompanyResponse(Long companyId, String companyName) {} | |
| public record CompanyResponse(Long companyId, String companyName, String companyLogoUrl) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,26 +25,35 @@ public class Company { | |
| @Column(name = "company_name", columnDefinition = "varchar(20)", unique = true) | ||
| private String name; | ||
|
|
||
| @Column(name = "search_name", columnDefinition = "varchar(80)") | ||
| private String searchName; | ||
|
Comment on lines
+31
to
+32
|
||
|
|
||
| @Column(name = "company_logo_url", columnDefinition = "varchar(2048)") | ||
| private String logoUrl; | ||
|
|
||
| @Column(nullable = false) | ||
| private boolean isSearchAllowed; | ||
|
|
||
| public void allowSearch() { | ||
| this.isSearchAllowed = true; | ||
| } | ||
|
|
||
| /* | ||
| Factory Method | ||
| */ | ||
| public static Company create(String name, String logoUrl, boolean isSearchAllowed) { | ||
| public static Company create(String name, String searchName, String logoUrl) { | ||
|
Comment on lines
-37
to
+47
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 검색용 이름이라고 하니까 비즈니스 로직을 도메인이 알고 있는 느낌이 드는데, 혹시 decomposedName 과 같은 변수명은 어떻게 생각하시나요? |
||
| return Company.builder() | ||
| .name(name) | ||
| .searchName(searchName) | ||
| .logoUrl(logoUrl) | ||
| .isSearchAllowed(isSearchAllowed) | ||
| .isSearchAllowed(false) | ||
| .build(); | ||
| } | ||
|
|
||
| @Builder(access = AccessLevel.PRIVATE) | ||
| private Company(String name, String logoUrl, boolean isSearchAllowed) { | ||
| private Company(String name, String searchName, String logoUrl, boolean isSearchAllowed) { | ||
| this.name = name; | ||
| this.searchName = searchName; | ||
| this.logoUrl = logoUrl; | ||
| this.isSearchAllowed = isSearchAllowed; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,26 @@ | ||
| package com.shyashyashya.refit.domain.company.repository; | ||
|
|
||
| import com.shyashyashya.refit.domain.company.api.response.CompanyResponse; | ||
| import com.shyashyashya.refit.domain.company.model.Company; | ||
| import java.util.Optional; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
|
|
||
| public interface CompanyRepository extends JpaRepository<Company, Long> { | ||
| Optional<Company> findByName(String name); | ||
|
|
||
| @Query(value = """ | ||
| SELECT new com.shyashyashya.refit.domain.company.api.response.CompanyResponse(c.id, c.name) | ||
| FROM Company c | ||
| WHERE LOWER(c.searchName) LIKE LOWER(CONCAT(:query, '%')) | ||
| AND c.isSearchAllowed = TRUE | ||
| """, countQuery = """ | ||
| SELECT COUNT(c) | ||
| FROM Company c | ||
| WHERE LOWER(c.searchName) LIKE LOWER(CONCAT(:query, '%')) | ||
| AND c.isSearchAllowed = TRUE | ||
| """) | ||
| Page<CompanyResponse> findAllBySearchQuery(String query, Pageable pageable); | ||
|
Comment on lines
+15
to
+26
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쿼리 dsl 로 프로젝션하면 좋을 것 같습니다!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ++ 만약에 후속에서 하시겠다고 하면 투두 주석 남겨두면 좋을 것 같아요
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 후속에서 하겠습니다! |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.shyashyashya.refit.domain.company.service; | ||
|
|
||
| import com.shyashyashya.refit.domain.company.api.response.CompanyResponse; | ||
| import com.shyashyashya.refit.domain.company.repository.CompanyRepository; | ||
| import com.shyashyashya.refit.global.util.HangulUtil; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class CompanyService { | ||
|
|
||
| private final CompanyRepository companyRepository; | ||
| private final HangulUtil hangulUtil; | ||
|
|
||
| public Page<CompanyResponse> findCompanies(String query, Pageable pageable) { | ||
| query = (query == null || query.isBlank()) ? "" : hangulUtil.decompose(query); | ||
| return companyRepository.findAllBySearchQuery(query, pageable); | ||
|
Comment on lines
+19
to
+20
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null, blank 처리를 decompose 내부에서 빈문자 반환 처리하는 건 어떤 것 같으세요? |
||
| } | ||
|
Comment on lines
+18
to
+21
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.shyashyashya.refit.global.util; | ||
|
|
||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class HangulUtil { | ||
|
|
||
| // 초성 (19자) | ||
| private static final char[] CHOSUNG = { | ||
| 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' | ||
| }; | ||
|
|
||
| // 중성 (21자) | ||
| private static final char[] JUNGSUNG = { | ||
| 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ' | ||
| }; | ||
|
|
||
| // 종성 (28자, 0번째는 종성 없음) | ||
| private static final char[] JONGSUNG = { | ||
| '\0', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', | ||
| 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' | ||
| }; | ||
|
|
||
| public String decompose(String input) { | ||
| StringBuilder result = new StringBuilder(); | ||
|
|
||
| for (char c : input.toCharArray()) { | ||
|
|
||
| // 한글 완성형 음절이 아닌 경우는 그대로 추가 | ||
| if (!isHangleSyllable(c)) { | ||
| result.append(c); | ||
| continue; | ||
| } | ||
|
|
||
| int code = c - 0xAC00; | ||
|
|
||
| int chosungIndex = code / (21 * 28); | ||
| int jungsungIndex = (code % (21 * 28)) / 28; | ||
| int jongsungIndex = code % 28; | ||
|
|
||
| result.append(CHOSUNG[chosungIndex]); | ||
| result.append(JUNGSUNG[jungsungIndex]); | ||
|
|
||
| // 종성이 있는 경우에만 추가 | ||
| if (jongsungIndex > 0) { | ||
| result.append(JONGSUNG[jongsungIndex]); | ||
| } | ||
| } | ||
|
|
||
| return result.toString(); | ||
| } | ||
|
|
||
| // '가' ~ '힣' 사이의 한글 완성형 음절인지 확인하는 메서드 | ||
| public boolean isHangleSyllable(char c) { | ||
| return 0xAC00 <= c && c <= 0xD7A3; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,9 @@ | ||||||||||
| package com.shyashyashya.refit.fixture; | ||||||||||
|
|
||||||||||
| import com.shyashyashya.refit.domain.company.model.Company; | ||||||||||
| import com.shyashyashya.refit.global.util.HangulUtil; | ||||||||||
|
|
||||||||||
| public class CompanyFixture { | ||||||||||
|
|
||||||||||
| public static final Company TEST_COMPANY = Company.create("test company", "test.com/logo.png", true); | ||||||||||
| public static final Company TEST_COMPANY = Company.create("test company", new HangulUtil().decompose("test company"), "test.com/logo.png"); | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change in the According to the rule 'Avoid hardcoding business logic values in entity factory methods. Pass them as parameters to improve clarity and flexibility, leaving business rule enforcement to service layers.', it is recommended to modify the References
|
||||||||||
| public static final Company TEST_COMPANY = Company.create("test company", new HangulUtil().decompose("test company"), "test.com/logo.png"); | |
| public static Company createTestCompany(HangulUtil hangulUtil) { | |
| return Company.create("test company", hangulUtil.decompose("test company"), "test.com/logo.png"); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CompanyController is missing API documentation annotations. According to the codebase conventions, all controllers should have @tag annotation at the class level and @operation annotation at the method level to provide proper Swagger/OpenAPI documentation. Please add:
Example based on similar controllers in the codebase:
and