Ver Fonte

Bugfix: Update menu and product handling with category IDs and improve data structure

emrecevik106 há 1 mês atrás
pai
commit
d491c9b621

+ 10 - 0
src/actions/auth/register/index.ts

@@ -1,3 +1,6 @@
+import {
+    Menu 
+} from "../../../models/Menu";
 import {
     User
 } from "../../../models/User";
@@ -75,6 +78,13 @@ const register = async (input: RegisterInput): Promise<RegisterResult> => {
             slug,
             mail
         });
+        
+        await Menu.create({
+            userID: newUser._id,
+            isViewPrice: true,
+            isActive: true,
+            products: []
+        });
 
         await smsSend({
             userID: newUser._id.toString()

+ 0 - 1
src/actions/menu/addCategory/index.ts

@@ -36,7 +36,6 @@ const addCategory = async (userID: string, input: AddCategoryInput): Promise<Add
             payload: {
                 category: {
                     _id: newCategory._id.toString(),
-                    isActive: newCategory.isActive,
                     title: newCategory.title,
                     index: newCategory.index
                 }

+ 0 - 1
src/actions/menu/addCategory/types.ts

@@ -22,7 +22,6 @@ export interface AddCategoryResult {
             _id: string;
             title: string;
             index: number;
-            isActive: boolean;
         }
     };
 }

+ 44 - 6
src/actions/menu/addProduct/index.ts

@@ -1,3 +1,6 @@
+import {
+    Menu 
+} from "../../../models/Menu";
 import {
     Product
 } from "../../../models/Product";
@@ -10,7 +13,7 @@ const addProduct = async (body: any, context: { userID: string }): Promise<AddPr
         const {
             description,
             isAvailable,
-            categoryID,
+            categoryIDs,
             isActive,
             photos,
             price,
@@ -21,12 +24,12 @@ const addProduct = async (body: any, context: { userID: string }): Promise<AddPr
             userID
         } = context;
 
-        const normalizedCategoryID = Array.isArray(categoryID) 
-            ? categoryID 
-            : categoryID ? [categoryID] : [];
+        const normalizedCategoryIDs = Array.isArray(categoryIDs) 
+            ? categoryIDs 
+            : categoryIDs ? [categoryIDs] : [];
     
         const newProduct = await Product.create({
-            categoryID: normalizedCategoryID,
+            categoryIDs: normalizedCategoryIDs,
             userID: userID,
             description,
             isAvailable,
@@ -36,11 +39,46 @@ const addProduct = async (body: any, context: { userID: string }): Promise<AddPr
             price
         });
 
+        const activeMenu = await Menu.findOne({
+            userID: userID,
+            isActive: true
+        });
+
+        if (activeMenu) {
+            const existingCategoryIDs = new Set(
+                activeMenu.products.flatMap((p: any) => p.categoryIDs.map((id: any) => id.toString()))
+            );
+
+            const newCategoryIDs = normalizedCategoryIDs.filter(
+                (id: string) => !existingCategoryIDs.has(id.toString())
+            );
+
+            await Menu.findByIdAndUpdate(activeMenu._id, {
+                $push: {
+                    products: {
+                        productID: newProduct._id,
+                        categoryIDs: newCategoryIDs,
+                        isViewPrice: true
+                    }
+                }
+            });
+        }
+
         return {
             message: "product-created-successfully",
             code: 201,
             payload: {
-                product: newProduct
+                _id: newProduct._id,
+                categoryIDs: newProduct.categoryIDs,
+                userID: newProduct.userID,
+                title: newProduct.title,
+                description: newProduct.description,
+                price: newProduct.price,
+                photos: newProduct.photos,
+                isActive: newProduct.isActive,
+                isAvailable: newProduct.isAvailable,
+                createdAt: newProduct.createdAt,
+                updatedAt: newProduct.updatedAt
             }
         };
     } catch (error) {

+ 1 - 1
src/actions/menu/addProduct/types.ts

@@ -10,7 +10,7 @@ import {
 
 export class AddProductInput {
     @IsNotEmpty({ message: "categoryID-is-required" })
-    categoryID?: string | string[];
+    categoryIDs?: string | string[];
 
     @IsString()
     @IsNotEmpty({ message: "title-is-required" })

+ 12 - 0
src/actions/menu/deleteCategory/index.ts

@@ -1,6 +1,9 @@
 import {
     Category 
 } from "../../../models/Category";
+import {
+    Product 
+} from "../../../models/Product";
 import {
     DeleteCategoryInput,
     DeleteCategoryResult 
@@ -23,6 +26,15 @@ const deleteCategory = async (userID: string, input: DeleteCategoryInput): Promi
             };
         }
 
+        const productInCategory = await Product.findOne({
+            categoryID
+        });
+        if (productInCategory) {
+            return {
+                message: "category-has-products",
+                code: 409
+            };
+        }
         await Category.findByIdAndDelete(categoryID);
 
         return {

+ 28 - 4
src/actions/menu/deleteProduct/index.ts

@@ -1,3 +1,6 @@
+import {
+    Menu 
+} from "../../../models/Menu";
 import {
     Product
 } from "../../../models/Product";
@@ -8,25 +11,46 @@ import {
 const deleteProduct = async (body: any, context: { userID: string }): Promise<DeleteProductResult> => {
     try {
         const {
-            productID
+            productIDs
         } = body;
 
         const {
             userID
         } = context;
 
-        const deletedProduct = await Product.findOneAndDelete({
-            _id: productID,
+        const normalizedProductIDs = Array.isArray(productIDs)
+            ? productIDs
+            : productIDs ? [productIDs] : [];
+            
+        const result = await Product.deleteMany({
+            _id: {
+                $in: normalizedProductIDs 
+            },
             userID: userID
         });
 
-        if (!deletedProduct) {
+        if (result.deletedCount === 0) {
             return {
                 message: "product-not-found-or-unauthorized",
                 code: 404
             };
         }
 
+        await Menu.updateMany(
+            {
+                userID: userID 
+            },
+            {
+                $pull: {
+                    products: {
+                        productID: {
+                            $in: normalizedProductIDs 
+                        } 
+                    } 
+                } 
+            }
+        );
+        
         return {
             message: "product-deleted-successfully",
             code: 200

+ 3 - 4
src/actions/menu/deleteProduct/types.ts

@@ -4,13 +4,12 @@ import {
 } from "class-validator";
 
 export class DeleteProductInput {
-    @IsString()
-    @IsNotEmpty({ message: "productID-is-required" })
-    productID!: string;
+    @IsNotEmpty({ message: "productIDs-is-required" })
+    productIDs!: string | string[];
 }
 
 export type DeleteProductResult = {
     message: string;
-    code: number;
     payload?: any;
+    code: number;
 };

+ 26 - 3
src/actions/menu/getCategories/index.ts

@@ -1,16 +1,40 @@
 import {
     Category 
 } from "../../../models/Category";
+import {
+    Menu 
+} from "../../../models/Menu";
 import {
     GetCategoriesResult 
 } from "./types";
 
 const getCategories = async (userID: string): Promise<GetCategoriesResult> => {
     try {
+        const activeMenu = await Menu.findOne({
+            userID,
+            isActive: true
+        });
+
+        if (!activeMenu) {
+            return {
+                message: "no-active-menu",
+                code: 404
+            };
+        }
+        
+        const categoryIDs = [
+            ...new Set(
+                activeMenu.products.flatMap((p: any) => p.categoryIDs.map((id: any) => id.toString()))
+            )
+        ];
+
         const categories = await Category.find({
-            userID
+            userID,
+            _id: {
+                $in: categoryIDs 
+            }
         }).sort({
-            index: 1
+            index: 1 
         });
 
         return {
@@ -19,7 +43,6 @@ const getCategories = async (userID: string): Promise<GetCategoriesResult> => {
             payload: {
                 categories: categories.map(c => ({
                     _id: c._id.toString(),
-                    isActive: c.isActive,
                     title: c.title,
                     index: c.index
                 }))

+ 0 - 1
src/actions/menu/getCategories/types.ts

@@ -3,7 +3,6 @@ export interface GetCategoriesResult {
     code: number;
     payload?: {
         categories: {
-            isActive: boolean;
             title: string;
             index: number;
             _id: string;

+ 35 - 9
src/actions/menu/getProducts/index.ts

@@ -6,6 +6,9 @@ import {
     GetProductsResult,
     GetProductsInput
 } from "./types";
+import {
+    Menu 
+} from "../../../models/Menu";
 
 const getProducts = async (query: GetProductsInput, context: { userID: string }): Promise<GetProductsResult> => {
     try {
@@ -14,19 +17,35 @@ const getProducts = async (query: GetProductsInput, context: { userID: string })
         } = context;
 
         const {
-            categoryID
+            categoryIDs
         } = query;
 
         const matchStage: any = {
             userID: new mongoose.Types.ObjectId(userID)
         };
 
-        if (categoryID) {
-            matchStage.categoryID = {
-                $in: [new mongoose.Types.ObjectId(categoryID)]
+        if (categoryIDs) {
+            matchStage.categoryIDs = {
+                $in: [new mongoose.Types.ObjectId(categoryIDs)]
             };
         }
 
+        const activeMenu = await Menu.findOne({
+            userID: new mongoose.Types.ObjectId(userID),
+            isActive: true
+        });
+
+        if (!activeMenu) {
+            return {
+                message: "no-products-found-in-active-menu",
+                code: 404
+            };
+        }
+        const activeProductIDs = activeMenu.products.map((p) => p.productID);
+        matchStage._id = {
+            $in: activeProductIDs 
+        };
+        
         const products = await Product.aggregate([
             {
                 $match: matchStage
@@ -34,7 +53,7 @@ const getProducts = async (query: GetProductsInput, context: { userID: string })
             {
                 $lookup: {
                     from: "categories",
-                    localField: "categoryID",
+                    localField: "categoryIDs",
                     foreignField: "_id",
                     as: "categoryIDs"
                 }
@@ -48,10 +67,17 @@ const getProducts = async (query: GetProductsInput, context: { userID: string })
 
         const payload = products.map((product) => {
             return {
-                ...product,
-                categoryIDs: product.categoryID.map((cat: any) => {
-                    return cat._id.toString();
-                })
+                _id: product._id,
+                categoryIDs: product.categoryIDs.map((cat: any) => cat._id.toString()),
+                userID: product.userID,
+                title: product.title,
+                description: product.description,
+                price: product.price,
+                photos: product.photos,
+                isActive: product.isActive,
+                isAvailable: product.isAvailable,
+                createdAt: product.createdAt,
+                updatedAt: product.updatedAt
             };
         });
 

+ 1 - 1
src/actions/menu/getProducts/types.ts

@@ -6,7 +6,7 @@ import {
 export class GetProductsInput {
     @IsString()
     @IsOptional()
-    categoryID?: string;
+    categoryIDs?: string;
 }
 
 export type GetProductsResult = {

+ 0 - 1
src/actions/menu/updateCategory/index.ts

@@ -46,7 +46,6 @@ const updateCategory = async (userID: string, input: UpdateCategoryInput): Promi
             payload: {
                 category: {
                     _id: updatedCategory._id.toString(),
-                    isActive: updatedCategory.isActive,
                     title: updatedCategory.title,
                     index: updatedCategory.index
                 }

+ 0 - 5
src/actions/menu/updateCategory/types.ts

@@ -19,10 +19,6 @@ export class UpdateCategoryInput {
     @IsNumber()
     @IsOptional()
     index?: number;
-
-    @IsBoolean()
-    @IsOptional()
-    isActive?: boolean;
 }
 
 export interface UpdateCategoryResult {
@@ -33,7 +29,6 @@ export interface UpdateCategoryResult {
             _id: string;
             title: string;
             index: number;
-            isActive: boolean;
         }
     };
 }

+ 17 - 7
src/actions/menu/updateProduct/index.ts

@@ -11,7 +11,7 @@ const updateProduct = async (body: any, context: { userID: string }): Promise<Up
         const {
             description,
             isAvailable,
-            categoryID,
+            categoryIDs,
             productID,
             isActive,
             photos,
@@ -26,9 +26,9 @@ const updateProduct = async (body: any, context: { userID: string }): Promise<Up
         const updateData: any = {
         };
 
-        if (categoryID !== undefined) updateData.categoryID = Array.isArray(categoryID) 
-            ? categoryID 
-            : [categoryID];
+        if (categoryIDs !== undefined) updateData.categoryIDs = Array.isArray(categoryIDs) 
+            ? categoryIDs 
+            : [categoryIDs];
         if (title !== undefined) updateData.title = title;
         if (description !== undefined) updateData.description = description;
         if (price !== undefined) updateData.price = price;
@@ -65,9 +65,9 @@ const updateProduct = async (body: any, context: { userID: string }): Promise<Up
             {
                 $lookup: {
                     from: "categories",
-                    localField: "categoryID",
+                    localField: "categoryIDs",
                     foreignField: "_id",
-                    as: "categoryID"
+                    as: "categoryIDs"
                 }
             }
         ]);
@@ -76,7 +76,17 @@ const updateProduct = async (body: any, context: { userID: string }): Promise<Up
             message: "product-updated-successfully",
             code: 200,
             payload: {
-                product: aggregatedProduct[0]
+                _id: aggregatedProduct[0]._id,
+                categoryIDs: aggregatedProduct[0].categoryIDs.map((cat: any) => cat._id.toString()),
+                userID: aggregatedProduct[0].userID,
+                title: aggregatedProduct[0].title,
+                description: aggregatedProduct[0].description,
+                price: aggregatedProduct[0].price,
+                photos: aggregatedProduct[0].photos,
+                isActive: aggregatedProduct[0].isActive,
+                isAvailable: aggregatedProduct[0].isAvailable,
+                createdAt: aggregatedProduct[0].createdAt,
+                updatedAt: aggregatedProduct[0].updatedAt
             }
         };
 

+ 1 - 1
src/actions/menu/updateProduct/types.ts

@@ -13,7 +13,7 @@ export class UpdateProductInput {
     productID!: string;
 
     @IsOptional()
-    categoryID?: string | string[];
+    categoryIDs?: string | string[];
 
     @IsString()
     @IsOptional()

+ 1 - 6
src/models/Category.ts

@@ -5,7 +5,6 @@ import mongoose, {
 
 export interface ICategory extends Document {
     userID: mongoose.Types.ObjectId;
-    isActive: boolean;
     createdAt: Date;
     updatedAt: Date;
     title: string;
@@ -19,11 +18,7 @@ const CategorySchema = new Schema<ICategory>(
             type: String,
             trim: true
         },
-        index: Number,
-        isActive: {
-            type: Boolean,
-            default: true
-        }
+        index: Number
     },
     {
         timestamps: true

+ 5 - 0
src/models/Menu.ts

@@ -9,6 +9,7 @@ export interface IMenu extends Document {
     description?: string;
     isActive?: boolean;
     products: Array<{
+        categoryIDs: mongoose.Types.ObjectId[];
         productID: mongoose.Types.ObjectId;
         isViewPrice: boolean;
     }>;
@@ -35,6 +36,10 @@ const menuSchema = new Schema<IMenu>(
         },
         products: [
             {
+                categoryIDs: {
+                    type: [Schema.Types.ObjectId],
+                    default: []
+                },
                 productID: {
                     type: Schema.Types.ObjectId,
                     ref: "Product", // TODO ilerde product servis buna göre düzenlenecek aggregate fonskiyonu kullanılacak ref değil

+ 2 - 2
src/models/Product.ts

@@ -3,7 +3,7 @@ import mongoose, {
     Schema
 } from "mongoose";
 export interface IProduct extends Document {
-    categoryID: mongoose.Types.ObjectId[];
+    categoryIDs: mongoose.Types.ObjectId[];
     userID: mongoose.Types.ObjectId;
     photos: {
         coverPhotoPath: string;
@@ -18,7 +18,7 @@ export interface IProduct extends Document {
 
 const productSchema = new Schema<IProduct>(
     {
-        categoryID: {
+        categoryIDs: {
             type: [mongoose.Schema.Types.ObjectId],
             index: true,
             default: []