Skip to content

Commit

Permalink
Merge pull request #205 from Roger13579/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Roger13579 authored Jun 11, 2024
2 parents 60c5b2c + 3c40bc7 commit ecd8d1c
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 81 deletions.
19 changes: 12 additions & 7 deletions src/dto/cart/deleteItemDto.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { Types } from 'mongoose';

import { IUser } from '../../models/user';
import { DeleteCartType, IDeleteCartReq } from '../../types/cart.type';
import {
DeleteCartType,
IDeleteCartItem,
IDeleteCartReq,
} from '../../types/cart.type';
import { uniq } from 'lodash';

export class DeleteItemDTO {
private readonly _userId: Types.ObjectId;
private readonly _productIds?: Types.ObjectId[];
private readonly _items: IDeleteCartItem[];
private readonly _type: DeleteCartType;

get userId() {
return this._userId;
}

get productIds() {
return this._productIds;
get items() {
return this._items;
}

get type() {
Expand All @@ -25,9 +29,10 @@ export class DeleteItemDTO {
const { user, body } = req;
this._userId = new Types.ObjectId((user as IUser)._id as string);

this._productIds = uniq(body.productIds || []).map(
(id) => new Types.ObjectId(id),
);
this._items = uniq(body.products || []).map(({ productId, plan }) => ({
productId: new Types.ObjectId(productId),
plan,
}));
this._type = body.type;
}
}
11 changes: 9 additions & 2 deletions src/models/baseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,21 @@ export const schemaDef = {
required: true,
},
plan: {
name: String,
name: {
type: String,
trim: true,
required: true,
},
discount: {
type: Number,
max: 1,
max: 0.95,
min: 0.1,
required: true,
},
headCount: {
type: Number,
min: 2,
required: true,
},
},
};
Expand Down Expand Up @@ -118,5 +124,6 @@ export const productSnapshotSchemaDef = {
},
startAt: {
type: Date,
required: true,
},
};
27 changes: 22 additions & 5 deletions src/models/cart.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { Schema, model } from 'mongoose';
import { schemaOption, virtualSchemaOption } from '../utils/constants';
import { BaseModel, ModelName, schemaDef } from './baseModel';
import { IProductId } from '../types/product.type';
import { IPlan, IProductId } from '../types/product.type';
import { IUserId } from '../types/user.type';

const { userId, productId } = schemaDef;
const { userId, productId, plan } = schemaDef;

export interface IItem extends IProductId, BaseModel {
amount: number;
plan: IPlan;
}

const ItemSchema = new Schema<IItem>(
{
productId: { ...productId, unique: true },
productId,
amount: { type: Number, required: true, min: 1 },
plan: {
type: plan,
required: true,
},
},
{
...schemaOption,
_id: false,
...virtualSchemaOption,
},
{ ...schemaOption, _id: false, ...virtualSchemaOption },
);

ItemSchema.index({ productId: 1 }, { unique: true });
ItemSchema.index(
{
productId: 1,
'plan.name': 1,
'plan.discount': 1,
'plan.headCount': 1,
},
{ unique: true },
);

export interface ICart extends BaseModel, IUserId {
items: [IItem];
Expand Down
32 changes: 25 additions & 7 deletions src/models/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import {
import paginate from 'mongoose-paginate-v2';
import { ITagId } from '../types/tag.type';
import { ICommentId } from '../types/comment.type';
import { uniqBy, isEqual } from 'lodash';

export interface IProduct extends BaseModel, IProductSnapshot {
plans?: IPlan[];
plans: [IPlan];
startAt: Date;
endAt: Date;
sellStartAt: Date;
Expand All @@ -45,6 +46,18 @@ export interface IProduct extends BaseModel, IProductSnapshot {

const { commentId, tagId, photoPath, plan } = schemaDef;

const validateUniqPlans = (plans: IPlan[]) => {
const uniqueNames = uniqBy(plans, 'name');
const uniqueDiscounts = uniqBy(plans, 'discount');
const uniqueHeadCounts = uniqBy(plans, 'headCount');

return (
isEqual(plans, uniqueNames) &&
isEqual(plans, uniqueDiscounts) &&
isEqual(plans, uniqueHeadCounts)
);
};

const schema = new Schema<IProduct>(
{
...productSnapshotSchemaDef,
Expand All @@ -56,9 +69,10 @@ const schema = new Schema<IProduct>(
validator: function (amount: number) {
// TODO: 找到正確的 access 方式,不要 as unknown as IProduct
const { plans } = this as unknown as IProduct;
return !!plans
? amount > Math.max(...plans.map(({ headCount }) => headCount))
: true;
const counts = plans.map(({ headCount }) => headCount);

const sum = counts.reduce((acc, count) => acc + count, 0);
return amount > sum;
},
message: '總數量錯誤,請確認 plans 內部數量',
},
Expand All @@ -69,6 +83,11 @@ const schema = new Schema<IProduct>(
},
plans: {
type: [plan],
required: true,
validate: {
validator: validateUniqPlans,
message: '方案名稱、折扣與人數不可重複',
},
},
startAt: {
type: Date,
Expand Down Expand Up @@ -96,9 +115,8 @@ const schema = new Schema<IProduct>(
type: Date,
required: true,
validate: {
validator: (sellStartAt: Date) => {
return moment().isBefore(moment(sellStartAt), 'day');
},
validator: (sellStartAt: Date) =>
moment().isBefore(moment(sellStartAt), 'day'),
message: '販賣開始時間必須晚於現在時間至少一天',
},
},
Expand Down
79 changes: 56 additions & 23 deletions src/repository/cartRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import CartModel from '../models/cart';
import { updateOptions } from '../utils/constants';
import {
EditCartType,
ICartProduct,
ICartPagination,
IEditCartItem,
IDeleteItemParams,
IEditItemParams,
} from '../types/cart.type';
import { GetCartDTO } from '../dto/cart/getCartDto';
import { createGetCartPipeline } from '../utils/aggregate/cart/getCart.pipeline';
import { IUserId } from '../types/user.type';
import { IProductId } from '../types/product.type';

interface IEditItemProps extends IUserId {
item: IEditCartItem;
}

interface IDeleteItemProps extends IUserId, IProductId {}

export class CartRepository {
private itemElemMath = ({ productId, plan }: ICartProduct) => ({
productId,
...(plan && {
'plan.discount': plan.discount,
'plan.name': plan.name,
'plan.headCount': plan.headCount,
}),
});

public createCart = async (userId: Types.ObjectId) =>
await CartModel.create({ userId, items: [] });

Expand All @@ -35,36 +38,66 @@ export class CartRepository {
public findCartByUserId = async (userId: Types.ObjectId) =>
await CartModel.findOne({ userId });

public addItem = async ({ item, userId }: IEditItemProps) => {
const { productId, amount } = item;
public addItem = async ({ item, userId }: IEditItemParams) => {
const { productId, amount, plan } = item;

return await CartModel.findOneAndUpdate(
{ userId, 'items.productId': { $ne: productId } },
{ $push: { items: { productId, amount } } },
updateOptions,
);
const filter = {
userId,
items: {
$not: {
$elemMatch: this.itemElemMath(item),
},
},
};

const update = { $push: { items: { productId, amount, plan } } };

return await CartModel.findOneAndUpdate(filter, update, updateOptions);
};

public clearCart = async (userId: Types.ObjectId) =>
await CartModel.findOneAndUpdate({ userId }, { items: [] });

public deleteItem = async ({ userId, productId }: IDeleteItemProps) =>
await CartModel.findOneAndUpdate(
{ userId, 'items.productId': { $eq: productId } },
{ $pull: { items: { productId } } },
public deleteItem = async (props: IDeleteItemParams) => {
const { userId, item } = props;
const filter = {
userId,
items: {
$elemMatch: this.itemElemMath(item),
},
};
return await CartModel.findOneAndUpdate(
filter,
{
$pull: {
items: {
productId: item.productId,
'plan.discount': item.plan.discount,
'plan.headCount': item.plan.headCount,
'plan.name': item.plan.name,
},
},
},
updateOptions,
);
};

public editItem = async ({ item, userId }: IEditItemProps) => {
const { productId, amount, type } = item;
const filter = { userId, 'items.productId': { $eq: productId } };
public editItem = async ({ item, userId }: IEditItemParams) => {
const { amount, type } = item;
const filter = {
userId,
items: {
$elemMatch: this.itemElemMath(item),
},
};
const updateAmountQuery = {
'items.$.amount': amount,
};
const update = {
...(type === EditCartType.set && { $set: updateAmountQuery }),
...(type === EditCartType.inc && { $inc: updateAmountQuery }),
};

return await CartModel.findOneAndUpdate(filter, update, {
...updateOptions,
upsert: false,
Expand Down
Loading

0 comments on commit ecd8d1c

Please sign in to comment.