diff --git a/src/dto/product/getProductDetailDto.ts b/src/dto/product/getProductDetailDto.ts index 9758874..843ae17 100644 --- a/src/dto/product/getProductDetailDto.ts +++ b/src/dto/product/getProductDetailDto.ts @@ -10,6 +10,11 @@ import { export class GetProductDetailDTO { private readonly _productId: Types.ObjectId; private readonly _isAdmin: boolean = false; + private readonly _user: IUser | undefined; + + get user(): IUser | undefined { + return this._user; + } get filter() { return { @@ -33,5 +38,6 @@ export class GetProductDetailDTO { if ((user as IUser | undefined)?.accountType === AccountType.admin) { this._isAdmin = true; } + this._user = req.user != null ? (req.user as IUser) : undefined; } } diff --git a/src/dto/product/getProductDto.ts b/src/dto/product/getProductDto.ts index c581601..df683d4 100644 --- a/src/dto/product/getProductDto.ts +++ b/src/dto/product/getProductDto.ts @@ -7,7 +7,7 @@ import { ProductSortField, } from '../../types/product.type'; import { AccountType } from '../../types/user.type'; -import { omitBy, isNil } from 'lodash'; +import { omitBy, isNil, isNaN } from 'lodash'; import { defaultProjection } from '../../utils/product.constants'; import { SortOrder } from '../../types/common.type'; @@ -31,6 +31,11 @@ export class GetProductDTO { private readonly _limit: number; private readonly _sort: Record; private readonly _isAdmin: boolean = false; + private readonly _user: IUser | undefined; + + get user() { + return this._user; + } get startAtFrom() { return this._startAtFrom; @@ -151,6 +156,7 @@ export class GetProductDTO { if ((req.user as IUser)?.accountType === AccountType.admin) { this._isAdmin = true; } + this._user = req.user != null ? (req.user as IUser) : undefined; this._types = types?.split(',') as ProductType[]; this._genres = genres?.split(',') as MovieGenre[]; diff --git a/src/routes/ticketRoute.ts b/src/routes/ticketRoute.ts index 0f05a3b..9413961 100644 --- a/src/routes/ticketRoute.ts +++ b/src/routes/ticketRoute.ts @@ -44,6 +44,7 @@ export class TicketRoute extends BaseRoute { required: false, description: '精準搜尋:票券狀態', type: 'string', + enum: ["verified", "unverified", "refunded", "expired", "cancelled", "pending", "transfer"], schema:{ $ref:"#/definitions/CustomGetTicketStatusQuery" } diff --git a/src/service/productService.ts b/src/service/productService.ts index eb6d928..e3519e0 100644 --- a/src/service/productService.ts +++ b/src/service/productService.ts @@ -11,7 +11,10 @@ import { TagRepository } from '../repository/tagRepository'; import { GetProductDetailDTO } from '../dto/product/getProductDetailDto'; import { SortOrder } from '../types/common.type'; import { IProduct } from '../models/product'; -import { TCreateInvalidProductParam } from '../types/product.type'; +import { + ProductDocumentWithFavorite, + TCreateInvalidProductParam, +} from '../types/product.type'; import { ITagId } from '../types/tag.type'; export class ProductService { @@ -62,7 +65,7 @@ export class ProductService { }; public findProducts = async (getProductDto: GetProductDTO) => { - const { sellStartAtFrom, sellStartAtTo, startAtFrom, startAtTo } = + const { sellStartAtFrom, sellStartAtTo, startAtFrom, startAtTo, user } = getProductDto; // 確認時間順序 @@ -76,15 +79,37 @@ export class ProductService { ], SortOrder.asc, ); + const result = await this.productRepository.findProducts(getProductDto); - return await this.productRepository.findProducts(getProductDto); + let favoriteProductIds: string[]; + if (user && user.favorites) { + favoriteProductIds = user.favorites.map((favorite) => + favorite.productId.toString(), + ); + } + result.docs = await Promise.all( + result.docs.map(async (doc) => { + let isFavorite = false; + if (user) { + if (favoriteProductIds.includes(doc._id.toString())) { + isFavorite = true; + } + } + return { + ...doc.toObject(), + isFavorite: isFavorite, + } as ProductDocumentWithFavorite; + }), + ); + return result; }; public getProductDetail = async ( getProductDetailDto: GetProductDetailDTO, ) => { - const product = - await this.productRepository.findProductDetail(getProductDetailDto); + const product = (await this.productRepository.findProductDetail( + getProductDetailDto, + )) as ProductDocumentWithFavorite; if (!product) { throwError( @@ -92,7 +117,17 @@ export class ProductService { CustomResponseType.PRODUCT_NOT_FOUND, ); } - return product; + let favoriteProductIds: string[] = []; + if (getProductDetailDto.user && getProductDetailDto.user.favorites) { + favoriteProductIds = getProductDetailDto.user.favorites.map((favorite) => + favorite.productId.toString(), + ); + } + + return { + ...product.toObject(), + isFavorite: favoriteProductIds.includes(product._id.toString()), + }; }; public deleteProducts = async (ids: Types.ObjectId[]) => { diff --git a/src/swagger/definition/product/general.ts b/src/swagger/definition/product/general.ts index d148d72..5b14960 100644 --- a/src/swagger/definition/product/general.ts +++ b/src/swagger/definition/product/general.ts @@ -50,6 +50,7 @@ export const ProductItem = { startAt: moment().add(2, 'day').add(4, 'hour').toISOString(), tags: [{ tagId: '123' }, { tagId: '123' }], photoPath: '', + isFavorite: false, }; /** @@ -85,6 +86,7 @@ export const GetProductDetailSuccess = { $startAt: moment().add(2, 'day').add(4, 'hour').toISOString(), $tags: [{ tagId: '123' }, { tagId: '123' }], $photoPath: '', + $isFavorite: false, ...ProductDetailItem, }, }; diff --git a/src/swagger/definition/user/general.ts b/src/swagger/definition/user/general.ts index c8568f4..808457f 100644 --- a/src/swagger/definition/user/general.ts +++ b/src/swagger/definition/user/general.ts @@ -2,19 +2,17 @@ import { CustomResponseType } from '../../../types/customResponseType'; import { PaginationSuccess } from '../common'; const favorite = { - product: { - $_id: '665b00748f529f5f17923acd', - title: '這是個很棒的電影名稱喔', - type: 'premier', - genre: 'action', - price: 1100, - soldAmount: 0, - amount: 100, - isLaunched: true, - photoPath: 'https://images.unsplash.com/photo-1554080353-a576cf803bda', - sellStartAt: new Date().toISOString(), - sellEndAt: new Date().toISOString(), - }, + $_id: '665b00748f529f5f17923acd', + title: '這是個很棒的電影名稱喔', + type: 'premier', + genre: 'action', + price: 1100, + soldAmount: 0, + amount: 100, + isLaunched: true, + photoPath: 'https://images.unsplash.com/photo-1554080353-a576cf803bda', + sellStartAt: new Date().toISOString(), + sellEndAt: new Date().toISOString(), }; export const GetFavoriteSuccess = { @@ -32,7 +30,7 @@ export const EditFavoriteSuccess = { $data: { $favorites: [ { - $productId: favorite.product.$_id, + $productId: favorite.$_id, }, ], }, diff --git a/src/types/cart.type.ts b/src/types/cart.type.ts index fd5193a..13fa579 100644 --- a/src/types/cart.type.ts +++ b/src/types/cart.type.ts @@ -48,6 +48,7 @@ interface ICartPaginationItem extends ITimestamp { | 'amount' | 'isLaunched' | 'photoPath' + | 'sellStartAt' | 'sellEndAt' >; amount: number; diff --git a/src/types/product.type.ts b/src/types/product.type.ts index 997494f..b36edd2 100644 --- a/src/types/product.type.ts +++ b/src/types/product.type.ts @@ -155,4 +155,13 @@ export interface IInvalidProduct extends ISubResponse { product: unknown; } +export interface IProductWithFavorite extends IProduct { + isFavorite: boolean; +} + +export type ProductDocumentWithFavorite = Document & + IProductWithFavorite & { + _id: Types.ObjectId; + }; + export type TCreateInvalidProductParam = (product: unknown) => IInvalidProduct;