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
12 changes: 11 additions & 1 deletion src/features/product/dto/inputs/store-products.input.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';

export class StoreProductsInput {
@IsString()
@IsNotEmpty()
storeId!: string;

@IsOptional()
@IsString()
@IsNotEmpty()
categoryId?: string;

@IsOptional()
Expand All @@ -14,6 +23,7 @@ export class StoreProductsInput {

@IsOptional()
@IsString()
@IsNotEmpty()
cursor?: string;

@IsOptional()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ describe('calcDiscountRate', () => {
it('정가가 0 이하이면 0', () => {
expect(calcDiscountRate(0, 0)).toBe(0);
});

it('salePrice가 음수 등 비정상이면 0~100으로 clamp한다', () => {
expect(calcDiscountRate(40000, -10000)).toBe(100);
});
});

describe('toStoreProduct', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export function calcDiscountRate(
if (salePrice === null || regularPrice <= 0 || salePrice >= regularPrice) {
return 0;
}
return Math.round((1 - salePrice / regularPrice) * 100);
// salePrice가 음수 등 비정상 값이어도 공개 계약(0~100)을 지키도록 clamp
const rate = Math.round((1 - salePrice / regularPrice) * 100);
return Math.min(100, Math.max(0, rate));
}

export function toStoreProduct(row: StoreProductRow): StoreProduct {
Expand Down
26 changes: 25 additions & 1 deletion src/features/product/services/product-storefront.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ describe('ProductStorefrontService (real DB)', () => {
expect(result).toEqual([]);
});

it('비활성/삭제 매장의 카테고리는 노출하지 않는다', async () => {
it('비활성 매장의 카테고리는 노출하지 않는다', async () => {
const store = await createStore(prisma, { is_active: false });
const product = await createProduct(prisma, { store_id: store.id });
const cat = await prisma.category.create({
Expand All @@ -353,5 +353,29 @@ describe('ProductStorefrontService (real DB)', () => {

expect(result).toEqual([]);
});

it('soft-delete된 매장의 카테고리는 노출하지 않는다', async () => {
const store = await createStore(prisma);
const product = await createProduct(prisma, { store_id: store.id });
const cat = await prisma.category.create({
data: {
name: '돌잔치',
category_type: 'EVENT',
sort_order: 0,
is_active: true,
},
});
await prisma.productCategory.create({
data: { product_id: product.id, category_id: cat.id },
});
await prisma.store.update({
where: { id: store.id },
data: { deleted_at: new Date() },
});

const result = await service.storeProductCategories(store.id.toString());

expect(result).toEqual([]);
});
});
});
11 changes: 10 additions & 1 deletion src/features/store/dto/inputs/store-reviews.input.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';

export class StoreReviewsInput {
@IsString()
@IsNotEmpty()
storeId!: string;

@IsOptional()
@IsString()
@IsNotEmpty()
cursor?: string;

@IsOptional()
Expand Down
2 changes: 1 addition & 1 deletion src/features/store/services/store-detail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class StoreDetailService {

const [reviewStats, wishlistedIds] = await Promise.all([
this.repo.aggregateReviewStats([storeId]),
accountId
accountId !== undefined
? this.wishlistRepo.findWishlistedStoreIds({
accountId,
storeIds: [storeId],
Expand Down
2 changes: 1 addition & 1 deletion src/features/store/services/store-review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class StoreReviewService {

const [likeCounts, likedIds] = await Promise.all([
this.repo.aggregateLikeCounts(reviewIds),
accountId
accountId !== undefined
? this.repo.findLikedReviewIds({ reviewIds, accountId })
: Promise.resolve(new Set<string>()),
]);
Expand Down
1 change: 1 addition & 0 deletions src/prisma/soft-delete.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const SOFT_DELETE_MODELS = new Set<Prisma.ModelName>([
'Store',
'StoreBusinessHour',
'StoreSpecialClosure',
'StoreImage',
'Category',
'Tag',
'Product',
Expand Down
Loading