Merge branch 'iipbs/sn/dev' into iipbs/dm/dev

This commit is contained in:
2026-03-27 14:36:57 +09:00
69 changed files with 8725 additions and 362 deletions
@@ -48,6 +48,16 @@ type SuggestionSeed = {
bldgNoMno: number;
};
/**
* 주소 검색 API를 호출해 우편번호/주소 목록을 조회한다.
* @param searchQuery 조회 조건 객체
* @param searchQuery.roadNm 도로명
* @param searchQuery.bldgNoMno 건물 본번
* @param searchQuery.bldgNoSno 건물 부번
* @param searchQuery.page 0부터 시작하는 페이지 번호
* @param searchQuery.size 페이지당 조회 건수
* @returns 주소 목록과 전체 건수를 포함한 응답 객체
*/
async function findAddress(searchQuery: ZipSearchParams): Promise<ZipListResponse> {
if (!searchQuery) return {};
@@ -68,6 +78,12 @@ async function findAddress(searchQuery: ZipSearchParams): Promise<ZipListRespons
return (await response.json()) as ZipListResponse;
}
/**
* 자동완성 후보를 만들기 위해 주소 항목에서 도로명과 건물 본번을 추출한다.
* 우선 API 응답 필드의 값을 사용하고, 값이 없으면 표시 주소 문자열을 파싱해 보정한다.
* @param item 주소 검색 결과 1건
* @returns 자동완성 생성에 사용할 도로명/건물본번 또는 추출 실패 시 null
*/
function extractSuggestionSeed(item: Zip): SuggestionSeed | null {
const ext = item as Zip & {
roadNm?: string;
@@ -97,6 +113,11 @@ function extractSuggestionSeed(item: Zip): SuggestionSeed | null {
return { roadNm, bldgNoMno };
}
/**
* 검색어에 허용되지 않는 특수문자나 SQL 예약어가 포함되어 있는지 검사한다.
* @param search 사용자가 입력한 검색 문자열
* @returns 검색 가능하면 true, 차단 대상이면 false
*/
function searchFilter(search: string): boolean {
if (search.length === 0) return true;
@@ -132,6 +153,13 @@ function searchFilter(search: string): boolean {
return true;
}
/**
* 단일 입력 주소 문자열을 "도로명 + 건물번호" 구조로 파싱해 API 조회 파라미터로 변환한다.
* 예) "테헤란로 152-1" -> { roadNm: "테헤란로", bldgNoMno: 152, bldgNoSno: 1, page }
* @param input 사용자가 입력한 주소 검색어
* @param page 1부터 시작하는 요청 페이지 번호
* @returns 파싱 성공 시 API 조회 파라미터와 정규화된 검색어, 실패 시 오류 메시지
*/
export function parsePostcodeInput(
input: string,
page = 1,
@@ -205,6 +233,12 @@ export function parsePostcodeInput(
};
}
/**
* 전체 도로명 주소 문자열에서 재검색에 사용할 "도로명 건물번호" 입력값만 추출한다.
* 예) "테헤란로 152, OO빌딩" -> "테헤란로 152"
* @param roadAddr 화면에 표시된 도로명 주소 문자열
* @returns 재검색 입력란에 넣을 수 있는 문자열, 추출 실패 시 빈 문자열
*/
export function extractSearchInputFromRoadAddr(roadAddr: string): string {
const firstSegment = roadAddr.split(",")[0]?.trim() ?? "";
if (!firstSegment) return "";
@@ -216,6 +250,12 @@ export function extractSearchInputFromRoadAddr(roadAddr: string): string {
return `${parts.slice(0, -1).join(" ")} ${bldgToken}`.trim();
}
/**
* 사용자가 입력 중인 도로명을 기준으로 자동완성 후보를 조회한다.
* 건물번호가 함께 입력된 경우에는 마지막 숫자 토큰을 제외한 도로명만으로 후보를 찾는다.
* @param input 사용자가 입력창에 입력한 원본 문자열
* @returns 최대 5개의 "도로명 건물본번" 자동완성 문자열 목록
*/
export async function fetchPostcodeSuggestions(input: string): Promise<string[]> {
const normalized = input.replace(/\s+/g, " ").trim();
const parts = normalized.split(" ");
@@ -252,6 +292,15 @@ export async function fetchPostcodeSuggestions(input: string): Promise<string[]>
return suggestions;
}
/**
* 우편번호 검색 API를 호출해 화면 표시용 우편번호 목록 형태로 변환한다.
* @param params 조회 조건 객체
* @param params.roadNm 검색할 도로명
* @param params.bldgNoMno 건물 본번
* @param params.bldgNoSno 건물 부번
* @param params.page 1부터 시작하는 화면 페이지 번호
* @returns 전체 건수와 화면 표시용 주소 목록
*/
export async function fetchPostcode(
params: PostcodeSearchParams,
): Promise<PostcodeSearchResult> {