Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/features/product/product-home.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type RandomCakesResult {
"""랜덤 케이크 그리드 셀(클릭 시 케이크 상세 이동)."""
type RandomCake {
id: ID!
"""소속 매장 ID(상세 URL 구성용: /store/{storeId}/products/{id})."""
storeId: ID!
"""대표 이미지(sort_order 최소)."""
thumbnailUrl: String!
}
Expand Down Expand Up @@ -79,6 +81,8 @@ input CustomCakeShowcaseInput {
type CustomCakeShowcaseItem {
"""후기 보러가기(reviewDetail) 이동용."""
reviewId: ID!
"""후기 소속 매장 ID(상세 URL 구성용: /store/{storeId}/reviews/{reviewId})."""
storeId: ID!
"""좋아요순 순위(1부터)."""
rank: Int!
"""작성자 닉네임. 탈퇴 작성자는 null(FE 익명 표기)."""
Expand All @@ -95,6 +99,8 @@ type CustomCakeShowcaseItem {
"""인기 케이크 카드(지역명·매장명·케이크명·가격·할인율)."""
type PopularCake {
id: ID!
"""소속 매장 ID(상세 URL 구성용: /store/{storeId}/products/{id})."""
storeId: ID!
rank: Int!
name: String!
"""대표 이미지(sort_order 최소). 없으면 null."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export class ProductReviewRepository {
async findShowcaseReviewRowsByIds(reviewIds: bigint[]): Promise<
{
id: bigint;
store_id: bigint;
content: string | null;
account: {
user_profile: { nickname: string; deleted_at: Date | null } | null;
Expand All @@ -216,6 +217,7 @@ export class ProductReviewRepository {
where: { id: { in: reviewIds }, deleted_at: null },
select: {
id: true,
store_id: true,
content: true,
account: {
// soft-delete extension은 nested relation에 deleted_at을 주입하지 않으므로
Expand Down
7 changes: 6 additions & 1 deletion src/features/product/repositories/product.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface StoreProductCategoryRow {
/** 인기 케이크 랭킹 후보 row. product-home 매퍼 입력. */
export interface CakeCandidateRow {
id: bigint;
store_id: bigint;
name: string;
regular_price: number;
sale_price: number | null;
Expand Down Expand Up @@ -1000,6 +1001,7 @@ export class ProductRepository {
},
select: {
id: true,
store_id: true,
name: true,
regular_price: true,
sale_price: true,
Expand Down Expand Up @@ -1200,7 +1202,9 @@ export class ProductRepository {
async findRandomCakeRows(args: {
productIds: bigint[];
categoryId?: bigint;
}): Promise<{ id: bigint; images: { image_url: string }[] }[]> {
}): Promise<
{ id: bigint; store_id: bigint; images: { image_url: string }[] }[]
> {
if (args.productIds.length === 0) return [];
return this.prisma.product.findMany({
where: {
Expand All @@ -1227,6 +1231,7 @@ export class ProductRepository {
},
select: {
id: true,
store_id: true,
images: {
where: { deleted_at: null },
orderBy: { sort_order: 'asc' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('ProductHome Query Resolver (real DB)', () => {

expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
storeId: orderItem.store_id.toString(),
reviewText: '제작 후기',
beforeImageUrl: 'https://img/before.png',
afterImageUrl: 'https://img/after.png',
Expand All @@ -97,7 +98,11 @@ describe('ProductHome Query Resolver (real DB)', () => {
const result = await resolver.randomCakes();

expect(result.items).toEqual([
{ id: cake.id.toString(), thumbnailUrl: 'https://img/grid.png' },
{
id: cake.id.toString(),
storeId: store.id.toString(),
thumbnailUrl: 'https://img/grid.png',
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function toPopularCake(
): PopularCake {
return {
id: row.id.toString(),
storeId: row.store_id.toString(),
rank,
name: row.name,
thumbnailUrl: row.images[0]?.image_url ?? null,
Expand Down
2 changes: 2 additions & 0 deletions src/features/product/services/product-home.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ describe('ProductHomeService (real DB)', () => {

expect(item).toMatchObject({
name: '레터링 케이크',
storeId: store.id.toString(),
storeName: '청담 케이크샵',
regionLabel: '서울 청담동',
regularPrice: 40000,
Expand Down Expand Up @@ -619,6 +620,7 @@ describe('ProductHomeService (real DB)', () => {
expect(new Set(result.items.map((i) => i.id)).size).toBe(9);
for (const item of result.items) {
expect(item.thumbnailUrl).toBe(`https://img/random-${item.id}.png`);
expect(item.storeId).toBe(store.id.toString());
}
});

Expand Down
5 changes: 4 additions & 1 deletion src/features/product/services/product-home.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class ProductHomeService {

items.push({
reviewId: row.id.toString(),
storeId: row.store_id.toString(),
rank: items.length + 1,
// 탈퇴(soft-delete) 작성자는 닉네임을 노출하지 않는다(익명화 정책)
authorNickname:
Expand Down Expand Up @@ -175,7 +176,9 @@ export class ProductHomeService {
const thumbnailUrl = row?.images[0]?.image_url;
// 후보 조회가 이미지 보유를 보장하지만, 조회 사이의 삭제 경합에 대비해 방어
if (!thumbnailUrl) return [];
return [{ id: id.toString(), thumbnailUrl }];
return [
{ id: id.toString(), storeId: row.store_id.toString(), thumbnailUrl },
];
});

return { items };
Expand Down
3 changes: 3 additions & 0 deletions src/features/product/types/product-home-output.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface HomeBanner {

export interface PopularCake {
id: string;
storeId: string;
rank: number;
name: string;
thumbnailUrl: string | null;
Expand All @@ -34,6 +35,7 @@ export interface PopularCakesResult {

export interface RandomCake {
id: string;
storeId: string;
thumbnailUrl: string;
}

Expand All @@ -43,6 +45,7 @@ export interface RandomCakesResult {

export interface CustomCakeShowcaseItem {
reviewId: string;
storeId: string;
rank: number;
authorNickname: string | null;
reviewText: string | null;
Expand Down
Loading