diff options
Diffstat (limited to 'lib/items/service.ts')
| -rw-r--r-- | lib/items/service.ts | 201 |
1 files changed, 163 insertions, 38 deletions
diff --git a/lib/items/service.ts b/lib/items/service.ts index 99ef79ef..35d2fa01 100644 --- a/lib/items/service.ts +++ b/lib/items/service.ts @@ -25,29 +25,105 @@ import { countItems, deleteItemById, deleteItemsByIds, findAllItems, insertItem, * Next.js의 unstable_cache를 사용해 일정 시간 캐시. */ export async function getItems(input: GetItemsSchema) { - + const safePerPage = Math.min(input.perPage, 100); + return unstable_cache( async () => { try { - const offset = (input.page - 1) * input.perPage; + const offset = (input.page - 1) * safePerPage; + + const advancedWhere = filterColumns({ + table: items, + filters: input.filters, + joinOperator: input.joinOperator, + }); + + let globalWhere; + if (input.search) { + const s = `%${input.search}%`; + globalWhere = or( + ilike(items.itemLevel, s), + ilike(items.itemCode, s), + ilike(items.itemName, s), + ilike(items.description, s), + ilike(items.parentItemCode, s), + ilike(items.unitOfMeasure, s), + ilike(items.steelType, s), + ilike(items.gradeMaterial, s), + ilike(items.baseUnitOfMeasure, s), + ilike(items.changeDate, s) + ); + } + + const finalWhere = and(advancedWhere, globalWhere); + + const orderBy = input.sort.length > 0 + ? input.sort.map((item) => + item.desc ? desc(items[item.id]) : asc(items[item.id]) + ) + : [asc(items.createdAt)]; + + const { data, total } = await db.transaction(async (tx) => { + const data = await selectItems(tx, { + where: finalWhere, + orderBy, + offset, + limit: safePerPage, + }); + + const total = await countItems(tx, finalWhere); + return { data, total }; + }); + + const pageCount = Math.ceil(total / safePerPage); + return { data, pageCount }; + } catch (err) { + console.error(err); + return { data: [], pageCount: 0 }; + } + }, + [JSON.stringify({...input, perPage: safePerPage})], + { + revalidate: 3600, + tags: ["items"], + } + )(); +} + +export interface GetItemsInfiniteInput extends Omit<GetItemsSchema, 'page' | 'perPage'> { + cursor?: string; + limit?: number; +} + +// 무한 스크롤 결과 타입 +export interface GetItemsInfiniteResult { + data: any[]; + hasNextPage: boolean; + nextCursor: string | null; + total?: number | null; +} - // const advancedTable = input.flags.includes("advancedTable"); - const advancedTable = true; +export async function getItemsInfinite(input: GetItemsInfiniteInput): Promise<GetItemsInfiniteResult> { + return unstable_cache( + async () => { + try { + // 페이지 크기 제한 (기존과 동일한 방식) + const safeLimit = Math.min(input.limit || 50, 100); - // advancedTable 모드면 filterColumns()로 where 절 구성 + // 고급 필터링 (기존과 완전 동일) const advancedWhere = filterColumns({ table: items, filters: input.filters, joinOperator: input.joinOperator, }); - - let globalWhere + // 전역 검색 (기존과 완전 동일) + let globalWhere; if (input.search) { - const s = `%${input.search}%` + const s = `%${input.search}%`; globalWhere = or( ilike(items.itemLevel, s), - ilike(items.itemCode, s), + ilike(items.itemCode, s), ilike(items.itemName, s), ilike(items.description, s), ilike(items.parentItemCode, s), @@ -56,58 +132,107 @@ export async function getItems(input: GetItemsSchema) { ilike(items.gradeMaterial, s), ilike(items.baseUnitOfMeasure, s), ilike(items.changeDate, s) - ) - // 필요시 여러 칼럼 OR조건 (status, priority, etc) + ); } - const finalWhere = and( - // advancedWhere or your existing conditions - advancedWhere, - globalWhere // and()함수로 결합 or or() 등으로 결합 - ) - + // 커서 기반 페이지네이션 조건 추가 + let cursorWhere; + if (input.cursor) { + cursorWhere = gt(items.id, input.cursor); + } - // 아니면 ilike, inArray, gte 등으로 where 절 구성 - const where = finalWhere - + // 모든 조건 결합 + const finalWhere = and(advancedWhere, globalWhere, cursorWhere); + + // 정렬 (기존과 동일하지만 id 정렬 보장) + let orderBy = input.sort.length > 0 + ? input.sort.map((item) => + item.desc ? desc(items[item.id]) : asc(items[item.id]) + ) + : [asc(items.createdAt)]; + + // 무한 스크롤에서는 id 정렬이 필수 (커서 기반 페이지네이션용) + const hasIdSort = orderBy.some(sort => { + const column = sort.constructor.name.includes('desc') + ? sort.column + : sort.column; + return column === items.id; + }); - const orderBy = - input.sort.length > 0 - ? input.sort.map((item) => - item.desc ? desc(items[item.id]) : asc(items[item.id]) - ) - : [asc(items.createdAt)]; + if (!hasIdSort) { + orderBy.push(asc(items.id)); + } - // 트랜잭션 내부에서 Repository 호출 + // 트랜잭션으로 데이터 조회 (기존과 동일한 패턴) const { data, total } = await db.transaction(async (tx) => { + // limit + 1로 다음 페이지 존재 여부 확인 const data = await selectItems(tx, { - where, + where: finalWhere, orderBy, - offset, - limit: input.perPage, + limit: safeLimit + 1, }); - const total = await countItems(tx, where); + // 첫 페이지에서만 전체 개수 계산 (성능 최적화) + let total = null; + if (!input.cursor) { + // 커서 조건 제외하고 전체 개수 계산 + const countWhere = and(advancedWhere, globalWhere); + total = await countItems(tx, countWhere); + } + return { data, total }; }); - const pageCount = Math.ceil(total / input.perPage); + // 다음 페이지 존재 여부 및 커서 설정 + const hasNextPage = data.length > safeLimit; + const resultItems = hasNextPage ? data.slice(0, safeLimit) : data; + const nextCursor = hasNextPage && resultItems.length > 0 + ? resultItems[resultItems.length - 1].id + : null; + + return { + data: resultItems, + hasNextPage, + nextCursor, + total, + }; - return { data, pageCount }; } catch (err) { - // 에러 발생 시 디폴트 - console.error(err) - return { data: [], pageCount: 0 }; + console.error('getItemsInfinite error:', err); + return { + data: [], + hasNextPage: false, + nextCursor: null, + total: 0, + }; } }, - [JSON.stringify(input)], // 캐싱 키 + [JSON.stringify({ ...input, limit: Math.min(input.limit || 50, 100) })], { revalidate: 3600, - tags: ["items"], // revalidateTag("items") 호출 시 무효화 + tags: ["items"], } )(); } +// 통합된 Items 조회 함수 (모드별 자동 분기) +export async function getItemsUnified(input: GetItemsSchema & { mode?: 'pagination' | 'infinite'; cursor?: string }): Promise<any> { + // perPage 기반 모드 자동 결정 + const isInfiniteMode = input.perPage >= 1_000_000; + + if (isInfiniteMode || input.mode === 'infinite') { + // 무한 스크롤 모드 + return getItemsInfinite({ + ...input, + limit: 50, // 실제로는 50개씩 로드 + cursor: input.cursor, + }); + } else { + // 기존 페이지네이션 모드 + return getItems(input); + } +} + /* ----------------------------------------------------- 2) 생성(Create) |
