From d627e3120d0c87d547c627306bd2700dbca4d015 Mon Sep 17 00:00:00 2001 From: chanwoo7 Date: Sun, 4 Oct 2026 23:59:32 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20BE=20=EC=8A=A4=ED=82=A4=EB=A7=88?= =?UTF-8?q?=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdminBanner.linkTargetAvailable 추가분(BE feat/admin-banner-link-target e76ccf9 기준) - BE 머지 뒤 develop 기준으로 다시 동기화한다 --- schema/schema.graphql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/schema/schema.graphql b/schema/schema.graphql index e2e6c8a..db9aca2 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -1,4 +1,4 @@ -# 생성 파일 — 수정하지 않는다. caquick-be local@7bece88a93b583c4c2d27ba42438cea7f01c9a0d 의 src/features/**/*.graphql 64개를 경로순으로 합쳤다. +# 생성 파일 — 수정하지 않는다. caquick-be local@e76ccf91dfa38aa98acc2b4449e0e76610eb1610 의 src/features/**/*.graphql 64개를 경로순으로 합쳤다. # 갱신: pnpm schema:pull [ref] # ---- src/features/audit-log/audit-log.types.graphql ---- @@ -3145,6 +3145,11 @@ type AdminBanner { """ linkCategoryId: ID """ + 링크 대상이 지금 노출 가능한지. 구매자 앱과 같은 기준(상품·매장·카테고리가 활성·미삭제, 상품은 소속 매장까지)이며, + false면 구매자 앱이 이 배너를 건너뛰고 다음 배너를 노출한다. linkType이 NONE·URL이면 항상 true. + """ + linkTargetAvailable: Boolean! + """ 노출 시작 일시. null이면 시작 제한 없음. """ startsAt: DateTime From 8836bd9796a6433b456353b5341561e98f803cd6 Mon Sep 17 00:00:00 2001 From: chanwoo7 Date: Sun, 4 Oct 2026 23:59:53 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EB=B0=B0=EB=84=88=20'=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EB=85=B8=EC=B6=9C'=EC=9D=B4=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=20=EB=8C=80=EC=83=81=20=EC=88=A8=EA=B9=80=20=EB=B0=B0=EB=84=88?= =?UTF-8?q?=EB=A5=BC=20=EA=B1=B4=EB=84=88=EB=9B=B0=EA=B3=A0=20'=EB=A7=81?= =?UTF-8?q?=ED=81=AC=20=EB=8C=80=EC=83=81=20=EC=88=A8=EA=B9=80'=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 연결한 상품·매장·카테고리가 나중에 숨겨지거나 삭제된 배너도 '현재 노출'로 표시했다. 구매자 앱은 그런 배너를 건너뛰고 다음 배너를 보여 주는데, 목록은 링크 대상 상태를 몰랐다. - AdminBanners·AdminBannersVisible 문서에 linkTargetAvailable 추가(BE가 구매자 조회와 같은 조건으로 판정) - currentBannerIds: linkTargetAvailable=false면 슬롯 후보에서 빼 구매자 앱과 같은 배너를 고름 - 목록 노출 상태 칸에 '링크 대상 숨김'(caution) 배지, 페이지 설명 문구 갱신 회귀 테스트 - live-status.spec: 대상 숨김인 상위 배너를 건너뛰고 다음 배너가 현재 노출 - banners.spec: 상위 배너는 '노출 중 + 링크 대상 숨김', 다음 배너가 '현재 노출' - 반증: 건너뛰기를 되돌리면 2건, 배지를 빼면 1건 실패 --- src/features/banners/api/queries.ts | 2 ++ src/features/banners/live-status.spec.ts | 16 +++++++++- src/features/banners/live-status.ts | 5 ++-- .../banners/pages/banners-list-page.tsx | 5 +++- src/features/banners/pages/banners.spec.tsx | 30 +++++++++++++++++++ src/graphql/generated/gql.ts | 12 ++++---- src/graphql/generated/graphql.ts | 6 ++-- 7 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/features/banners/api/queries.ts b/src/features/banners/api/queries.ts index 1770785..a9f459b 100644 --- a/src/features/banners/api/queries.ts +++ b/src/features/banners/api/queries.ts @@ -39,6 +39,7 @@ const AdminBannersDocument = graphql(/* GraphQL */ ` linkProductId linkStoreId linkCategoryId + linkTargetAvailable startsAt endsAt sortOrder @@ -101,6 +102,7 @@ const AdminBannersVisibleDocument = graphql(/* GraphQL */ ` id placement linkCategoryId + linkTargetAvailable startsAt endsAt sortOrder diff --git a/src/features/banners/live-status.spec.ts b/src/features/banners/live-status.spec.ts index 806b40e..7db41a0 100644 --- a/src/features/banners/live-status.spec.ts +++ b/src/features/banners/live-status.spec.ts @@ -5,7 +5,13 @@ const BEFORE = '2026-09-30T23:59:59.999Z'; const AFTER = '2026-10-01T00:00:00.001Z'; describe('배너 현재 노출', () => { - const live = { isActive: true, startsAt: null, endsAt: null, linkCategoryId: null }; + const live = { + isActive: true, + startsAt: null, + endsAt: null, + linkCategoryId: null, + linkTargetAvailable: true, + }; it.each([ { name: '슬롯마다 정렬 순서가 가장 작은 것', @@ -34,6 +40,14 @@ describe('배너 현재 노출', () => { ], want: ['4'], }, + { + name: '링크 대상이 숨김이면 구매자 앱처럼 건너뛰고 다음 배너', + banners: [ + { ...live, id: '1', placement: 'HOME_MAIN', sortOrder: 0, linkTargetAvailable: false }, + { ...live, id: '2', placement: 'HOME_MAIN', sortOrder: 1 }, + ], + want: ['2'], + }, { name: '카테고리 배치는 카테고리마다 따로 고른다', banners: [ diff --git a/src/features/banners/live-status.ts b/src/features/banners/live-status.ts index 381ab02..60ed6da 100644 --- a/src/features/banners/live-status.ts +++ b/src/features/banners/live-status.ts @@ -5,6 +5,7 @@ interface SlotBanner extends Timed { id: string; placement: BannerPlacement; linkCategoryId?: string | null; + linkTargetAvailable: boolean; sortOrder: number; } @@ -26,13 +27,13 @@ const byId = (a: string, b: string) => /** * 슬롯마다 지금 구매자에게 보이는 배너 1개의 ID. 노출 중인 것 가운데 정렬 순서가 가장 작고, 같으면 먼저 등록한(ID가 작은) 것. - * 연결한 상품·매장·카테고리가 숨김이면 구매자 앱은 다음 배너로 넘어가는데, 이 목록은 그 상태를 모른다. + * 연결한 상품·매장·카테고리가 숨김이면(linkTargetAvailable=false) 구매자 앱처럼 건너뛰고 다음 배너를 고른다. */ export function currentBannerIds(banners: readonly SlotBanner[], now: Date): Set { const winners = new Map(); for (const b of banners) { const key = slotKey(b); - if (key === null || liveStatus(b, now) !== 'LIVE') continue; + if (key === null || !b.linkTargetAvailable || liveStatus(b, now) !== 'LIVE') continue; const cur = winners.get(key); if ( !cur || diff --git a/src/features/banners/pages/banners-list-page.tsx b/src/features/banners/pages/banners-list-page.tsx index 52075fe..b3d6499 100644 --- a/src/features/banners/pages/banners-list-page.tsx +++ b/src/features/banners/pages/banners-list-page.tsx @@ -87,6 +87,9 @@ export function BannersListPage({ search, onSearchChange }: Props) { {s.label} {current.has(row.original.id) && 현재 노출} + {!row.original.linkTargetAvailable && ( + 링크 대상 숨김 + )} ); }, @@ -142,7 +145,7 @@ export function BannersListPage({ search, onSearchChange }: Props) { <> diff --git a/src/features/banners/pages/banners.spec.tsx b/src/features/banners/pages/banners.spec.tsx index 75e8cac..f3e76c3 100644 --- a/src/features/banners/pages/banners.spec.tsx +++ b/src/features/banners/pages/banners.spec.tsx @@ -27,6 +27,7 @@ const banner = { linkProductId: null, linkStoreId: null, linkCategoryId: null, + linkTargetAvailable: true, startsAt: null, endsAt: null, sortOrder: 0, @@ -117,6 +118,35 @@ describe('배너', () => { await vi.waitFor(() => expect(deleted).toEqual({ bannerId: '5' })); }); + it('목록: 링크 대상이 숨김인 배너는 현재 노출에서 건너뛰고 링크 대상 숨김으로 표시한다', async () => { + const rows = [ + { + ...banner, + id: '5', + title: '대상 숨김', + linkType: 'PRODUCT', + linkProductId: '3', + linkTargetAvailable: false, + }, + { ...banner, id: '6', title: '다음 배너', sortOrder: 1 }, + ]; + server.use( + gqlOk('AdminBanners', { + adminBanners: { items: rows, totalCount: 2, hasMore: false, nextCursor: null }, + }), + gqlOk('AdminBannersVisible', { + adminBanners: { items: rows, hasMore: false, nextCursor: null }, + }), + ); + boot('/banners'); + const statusOf = async (title: string) => + within((await screen.findByRole('link', { name: title })).closest('tr')!).getAllByRole( + 'cell', + )[2]!.textContent; + await vi.waitFor(async () => expect(await statusOf('다음 배너')).toBe('노출 중현재 노출')); + expect(await statusOf('대상 숨김')).toBe('노출 중링크 대상 숨김'); + }); + it('목록을 켜 둔 채 시작 시각이 되면 예약이 노출 중·현재 노출로 바뀐다', async () => { // 실제 시간도 흐르게 둬 부팅·요청은 그대로 진행되고, 시작 시각만 앞당겨 넘긴다 vi.useFakeTimers({ shouldAdvanceTime: true }); diff --git a/src/graphql/generated/gql.ts b/src/graphql/generated/gql.ts index af13946..c8019d1 100644 --- a/src/graphql/generated/gql.ts +++ b/src/graphql/generated/gql.ts @@ -26,12 +26,12 @@ type Documents = { "\n query AdminAuditLogs($input: AdminAuditLogListInput) {\n adminAuditLogs(input: $input) {\n items {\n id\n actorAccountId\n actorAccountType\n actorLabel\n storeId\n targetType\n targetId\n action\n beforeJson\n afterJson\n ipAddress\n userAgent\n createdAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n": typeof types.AdminAuditLogsDocument, "\n query AdminAuditStorePicker($input: AdminStoreListInput) {\n adminStores(input: $input) {\n items {\n id\n storeName\n isActive\n }\n }\n }\n": typeof types.AdminAuditStorePickerDocument, "\n query AdminAuditActorPicker($sellers: AdminSellerListInput, $admins: CursorInput) {\n adminSellers(input: $sellers) {\n items {\n accountId\n username\n name\n }\n }\n adminAdmins(input: $admins) {\n items {\n accountId\n username\n name\n }\n }\n }\n": typeof types.AdminAuditActorPickerDocument, - "\n query AdminBanners($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n": typeof types.AdminBannersDocument, + "\n query AdminBanners($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n linkTargetAvailable\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n": typeof types.AdminBannersDocument, "\n query AdminBanner($bannerId: ID!) {\n adminBanner(bannerId: $bannerId) {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n }\n": typeof types.AdminBannerDocument, "\n mutation AdminCreateBanner($input: AdminCreateBannerInput!) {\n adminCreateBanner(input: $input) {\n id\n }\n }\n": typeof types.AdminCreateBannerDocument, "\n mutation AdminUpdateBanner($input: AdminUpdateBannerInput!) {\n adminUpdateBanner(input: $input) {\n id\n updatedAt\n }\n }\n": typeof types.AdminUpdateBannerDocument, "\n mutation AdminDeleteBanner($bannerId: ID!) {\n adminDeleteBanner(bannerId: $bannerId)\n }\n": typeof types.AdminDeleteBannerDocument, - "\n query AdminBannersVisible($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n }\n hasMore\n nextCursor\n }\n }\n": typeof types.AdminBannersVisibleDocument, + "\n query AdminBannersVisible($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n linkCategoryId\n linkTargetAvailable\n startsAt\n endsAt\n sortOrder\n isActive\n }\n hasMore\n nextCursor\n }\n }\n": typeof types.AdminBannersVisibleDocument, "\n query AdminBannerProductOptions($input: AdminProductListInput) {\n adminProducts(input: $input) {\n items {\n id\n name\n storeName\n storeIsActive\n }\n }\n }\n": typeof types.AdminBannerProductOptionsDocument, "\n query AdminBannerStoreOptions($input: AdminStoreListInput) {\n adminStores(input: $input) {\n items {\n id\n storeName\n }\n }\n }\n": typeof types.AdminBannerStoreOptionsDocument, "\n query AdminBannerCategoryOptions($input: AdminCategoryListInput) {\n adminCategories(input: $input) {\n id\n name\n isActive\n }\n }\n": typeof types.AdminBannerCategoryOptionsDocument, @@ -104,12 +104,12 @@ const documents: Documents = { "\n query AdminAuditLogs($input: AdminAuditLogListInput) {\n adminAuditLogs(input: $input) {\n items {\n id\n actorAccountId\n actorAccountType\n actorLabel\n storeId\n targetType\n targetId\n action\n beforeJson\n afterJson\n ipAddress\n userAgent\n createdAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n": types.AdminAuditLogsDocument, "\n query AdminAuditStorePicker($input: AdminStoreListInput) {\n adminStores(input: $input) {\n items {\n id\n storeName\n isActive\n }\n }\n }\n": types.AdminAuditStorePickerDocument, "\n query AdminAuditActorPicker($sellers: AdminSellerListInput, $admins: CursorInput) {\n adminSellers(input: $sellers) {\n items {\n accountId\n username\n name\n }\n }\n adminAdmins(input: $admins) {\n items {\n accountId\n username\n name\n }\n }\n }\n": types.AdminAuditActorPickerDocument, - "\n query AdminBanners($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n": types.AdminBannersDocument, + "\n query AdminBanners($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n linkTargetAvailable\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n": types.AdminBannersDocument, "\n query AdminBanner($bannerId: ID!) {\n adminBanner(bannerId: $bannerId) {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n }\n": types.AdminBannerDocument, "\n mutation AdminCreateBanner($input: AdminCreateBannerInput!) {\n adminCreateBanner(input: $input) {\n id\n }\n }\n": types.AdminCreateBannerDocument, "\n mutation AdminUpdateBanner($input: AdminUpdateBannerInput!) {\n adminUpdateBanner(input: $input) {\n id\n updatedAt\n }\n }\n": types.AdminUpdateBannerDocument, "\n mutation AdminDeleteBanner($bannerId: ID!) {\n adminDeleteBanner(bannerId: $bannerId)\n }\n": types.AdminDeleteBannerDocument, - "\n query AdminBannersVisible($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n }\n hasMore\n nextCursor\n }\n }\n": types.AdminBannersVisibleDocument, + "\n query AdminBannersVisible($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n linkCategoryId\n linkTargetAvailable\n startsAt\n endsAt\n sortOrder\n isActive\n }\n hasMore\n nextCursor\n }\n }\n": types.AdminBannersVisibleDocument, "\n query AdminBannerProductOptions($input: AdminProductListInput) {\n adminProducts(input: $input) {\n items {\n id\n name\n storeName\n storeIsActive\n }\n }\n }\n": types.AdminBannerProductOptionsDocument, "\n query AdminBannerStoreOptions($input: AdminStoreListInput) {\n adminStores(input: $input) {\n items {\n id\n storeName\n }\n }\n }\n": types.AdminBannerStoreOptionsDocument, "\n query AdminBannerCategoryOptions($input: AdminCategoryListInput) {\n adminCategories(input: $input) {\n id\n name\n isActive\n }\n }\n": types.AdminBannerCategoryOptionsDocument, @@ -218,7 +218,7 @@ export function graphql(source: "\n query AdminAuditActorPicker($sellers: Admin /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query AdminBanners($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n"): typeof import('./graphql').AdminBannersDocument; +export function graphql(source: "\n query AdminBanners($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n title\n imageUrl\n linkType\n linkUrl\n linkProductId\n linkStoreId\n linkCategoryId\n linkTargetAvailable\n startsAt\n endsAt\n sortOrder\n isActive\n createdAt\n updatedAt\n }\n totalCount\n hasMore\n nextCursor\n }\n }\n"): typeof import('./graphql').AdminBannersDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -238,7 +238,7 @@ export function graphql(source: "\n mutation AdminDeleteBanner($bannerId: ID!) /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query AdminBannersVisible($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n linkCategoryId\n startsAt\n endsAt\n sortOrder\n isActive\n }\n hasMore\n nextCursor\n }\n }\n"): typeof import('./graphql').AdminBannersVisibleDocument; +export function graphql(source: "\n query AdminBannersVisible($input: AdminBannerListInput) {\n adminBanners(input: $input) {\n items {\n id\n placement\n linkCategoryId\n linkTargetAvailable\n startsAt\n endsAt\n sortOrder\n isActive\n }\n hasMore\n nextCursor\n }\n }\n"): typeof import('./graphql').AdminBannersVisibleDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/graphql/generated/graphql.ts b/src/graphql/generated/graphql.ts index 655c38b..3306926 100644 --- a/src/graphql/generated/graphql.ts +++ b/src/graphql/generated/graphql.ts @@ -873,7 +873,7 @@ export type AdminBannersQueryVariables = Exact<{ }>; -export type AdminBannersQuery = { adminBanners: { totalCount: number, hasMore: boolean, nextCursor: string | null, items: Array<{ id: string, placement: BannerPlacement, title: string | null, imageUrl: string, linkType: BannerLinkType, linkUrl: string | null, linkProductId: string | null, linkStoreId: string | null, linkCategoryId: string | null, startsAt: string | null, endsAt: string | null, sortOrder: number, isActive: boolean, createdAt: string, updatedAt: string }> } }; +export type AdminBannersQuery = { adminBanners: { totalCount: number, hasMore: boolean, nextCursor: string | null, items: Array<{ id: string, placement: BannerPlacement, title: string | null, imageUrl: string, linkType: BannerLinkType, linkUrl: string | null, linkProductId: string | null, linkStoreId: string | null, linkCategoryId: string | null, linkTargetAvailable: boolean, startsAt: string | null, endsAt: string | null, sortOrder: number, isActive: boolean, createdAt: string, updatedAt: string }> } }; export type AdminBannerQueryVariables = Exact<{ bannerId: string | number; @@ -908,7 +908,7 @@ export type AdminBannersVisibleQueryVariables = Exact<{ }>; -export type AdminBannersVisibleQuery = { adminBanners: { hasMore: boolean, nextCursor: string | null, items: Array<{ id: string, placement: BannerPlacement, linkCategoryId: string | null, startsAt: string | null, endsAt: string | null, sortOrder: number, isActive: boolean }> } }; +export type AdminBannersVisibleQuery = { adminBanners: { hasMore: boolean, nextCursor: string | null, items: Array<{ id: string, placement: BannerPlacement, linkCategoryId: string | null, linkTargetAvailable: boolean, startsAt: string | null, endsAt: string | null, sortOrder: number, isActive: boolean }> } }; export type AdminBannerProductOptionsQueryVariables = Exact<{ input?: AdminProductListInput | null | undefined; @@ -1507,6 +1507,7 @@ export const AdminBannersDocument = new TypedDocumentString(` linkProductId linkStoreId linkCategoryId + linkTargetAvailable startsAt endsAt sortOrder @@ -1568,6 +1569,7 @@ export const AdminBannersVisibleDocument = new TypedDocumentString(` id placement linkCategoryId + linkTargetAvailable startsAt endsAt sortOrder From 0a536fa307205dd85629556da40d3694c4909645 Mon Sep 17 00:00:00 2001 From: chanwoo7 Date: Mon, 5 Oct 2026 00:59:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20BE=20develop=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- schema/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/schema.graphql b/schema/schema.graphql index db9aca2..0466de5 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -1,4 +1,4 @@ -# 생성 파일 — 수정하지 않는다. caquick-be local@e76ccf91dfa38aa98acc2b4449e0e76610eb1610 의 src/features/**/*.graphql 64개를 경로순으로 합쳤다. +# 생성 파일 — 수정하지 않는다. caquick-be local@251a957ea22bb84be535aa3d6cb8a4214e5e585b 의 src/features/**/*.graphql 64개를 경로순으로 합쳤다. # 갱신: pnpm schema:pull [ref] # ---- src/features/audit-log/audit-log.types.graphql ----