2 Komitmen e9839bfb77 ... 988f5c7c43

Pembuat SHA1 Pesan Tanggal
  BedirhanOZCAN 988f5c7c43 Merge branch 'feature/recommendedProduct-endpoint-feature' into develop 1 bulan lalu
  BedirhanOZCAN 5d3aa831c8 Feature: Implement recommended product management endpoints and enhance registration error handling 1 bulan lalu

+ 3 - 1
src/actions/auth/register/index.ts

@@ -91,8 +91,10 @@ const register = async (input: RegisterInput): Promise<RegisterResult> => {
         });
 
         if (!starterPlan) {
+            console.error("CRITICAL ALARM: 'Starter' plan not found in the database! New registrations are failing. Please check the Plans collection immediately.");
+
             return {
-                message: "starter-plan-not-configured-in-database",
+                message: "a-temporary-issue-occurred-during-registration-please-contact-support",
                 code: 500
             };
         }

+ 74 - 0
src/actions/menu/addRecommendedProduct/index.ts

@@ -0,0 +1,74 @@
+import mongoose from "mongoose";
+import {
+    RecommendedProduct,
+    Product
+} from "../../../models";
+import {
+    AddRecommendedProductResult,
+    AddRecommendedProductInput
+} from "./types";
+
+const addRecommendedProduct = async (userID: string, recommendedProductLimit: number, input: AddRecommendedProductInput): Promise<AddRecommendedProductResult> => {
+    try {
+        const {
+            productID,
+            ...rest
+        } = input;
+
+        const currentCount = await RecommendedProduct.countDocuments({
+            userID: userID
+        });
+
+        if (currentCount >= recommendedProductLimit) {
+            return {
+                message: "recommended-product-limit-reached",
+                code: 403
+            };
+        }
+
+        const existingProduct = await Product.findOne({
+            _id: new mongoose.Types.ObjectId(productID),
+            userID: userID
+        });
+
+        if (!existingProduct) {
+            return {
+                message: "product-not-found-or-unauthorized",
+                code: 404
+            };
+        }
+
+        const alreadyRecommended = await RecommendedProduct.findOne({
+            productID: new mongoose.Types.ObjectId(productID),
+            userID: userID
+        });
+
+        if (alreadyRecommended) {
+            return {
+                message: "product-is-already-recommended",
+                code: 409
+            };
+        }
+
+        const newRecommendedProduct = await RecommendedProduct.create({
+            productID: new mongoose.Types.ObjectId(productID),
+            userID: userID,
+            ...rest
+        });
+
+        return {
+            message: "recommended-product-added-successfully",
+            code: 201,
+            payload: newRecommendedProduct
+        };
+
+    } catch (error) {
+        console.error("AddRecommendedProduct action error:", error);
+        return {
+            message: "internal-server-error",
+            code: 500
+        };
+    }
+};
+
+export default addRecommendedProduct;

+ 37 - 0
src/actions/menu/addRecommendedProduct/types.ts

@@ -0,0 +1,37 @@
+import {
+    IsNotEmpty,
+    IsOptional,
+    IsBoolean,
+    IsString,
+    IsNumber,
+    Min
+} from "class-validator";
+
+export class AddRecommendedProductInput {
+    @IsString({ message: "productID-must-be-a-string" })
+    @IsNotEmpty({ message: "productID-is-required" })
+    productID!: string;
+
+    @IsString({ message: "title-must-be-a-string" })
+    @IsNotEmpty({ message: "title-is-required" })
+    title!: string;
+
+    @IsString({ message: "description-must-be-a-string" })
+    @IsOptional()
+    description?: string;
+
+    @IsNumber({}, { message: "delay-must-be-a-number" })
+    @Min(0, { message: "delay-cannot-be-negative" })
+    @IsNotEmpty({ message: "delay-is-required" })
+    delay!: number;
+
+    @IsBoolean({ message: "is-active-must-be-a-boolean" })
+    @IsOptional()
+    isActive?: boolean;
+}
+
+export type AddRecommendedProductResult = {
+    message: string;
+    code: number;
+    payload?: any;
+};

+ 0 - 6
src/actions/menu/deleteMenu/index.ts

@@ -13,7 +13,6 @@ const deleteMenu = async (userID: string, input: DeleteMenuInput): Promise<Delet
             menuIDs
         } = input;
 
-        // Erken Çıkış
         if (menuIDs.length === 0) {
             return {
                 message: "menuIDs-cannot-be-empty",
@@ -21,11 +20,8 @@ const deleteMenu = async (userID: string, input: DeleteMenuInput): Promise<Delet
             };
         }
 
-        // KESİNLİKLE YAPILMASI GEREKEN TİP DÖNÜŞÜMÜ
         const objectIdArray = menuIDs.map(id => new mongoose.Types.ObjectId(id));
 
-        // KULLANICININ İSTEDİĞİ MENÜLER GERÇEKTEN VAR MI
-        // findOne yerine gönderilen ID sayısıyla, bulunan ID sayısını karşılaştırıyoruz
         const existingMenusCount = await Menu.countDocuments({
             _id: {
                 $in: objectIdArray
@@ -33,7 +29,6 @@ const deleteMenu = async (userID: string, input: DeleteMenuInput): Promise<Delet
             userID: userID
         });
 
-        // Kullanıcı 3 ID yolladı ama DB'de 2 tane bulduysak, biri uydurma veya başkasının menüsüdür.
         if (existingMenusCount !== objectIdArray.length) {
             return {
                 message: "one-or-more-menus-not-found-or-unauthorized",
@@ -54,7 +49,6 @@ const deleteMenu = async (userID: string, input: DeleteMenuInput): Promise<Delet
             isActive: true
         });
 
-        // Kullanıcının en az 1 tane aktif menüsü kalmak zorundadır
         if (activeMenuCount - deletingActiveCount < 1) {
             return {
                 message: "cannot-delete-last-active-menu",

+ 8 - 0
src/actions/menu/deleteProduct/index.ts

@@ -1,5 +1,6 @@
 import mongoose from "mongoose";
 import {
+    RecommendedProduct,
     Product,
     Menu
 } from "../../../models";
@@ -52,6 +53,13 @@ const deleteProduct = async (userID: string, input: DeleteProductInput): Promise
             }
         );
 
+        await RecommendedProduct.deleteMany({
+            productID: {
+                $in: objectIdArray
+            },
+            userID: userID
+        });
+
         return {
             message: "product-deleted-successfully",
             code: 200

+ 53 - 0
src/actions/menu/deleteRecommendedProduct/index.ts

@@ -0,0 +1,53 @@
+import mongoose from "mongoose";
+import {
+    RecommendedProduct
+} from "../../../models";
+import {
+    DeleteRecommendedProductResult,
+    DeleteRecommendedProductInput
+} from "./types";
+
+const deleteRecommendedProduct = async (userID: string, input: DeleteRecommendedProductInput): Promise<DeleteRecommendedProductResult> => {
+    try {
+        const {
+            recommendedProductIDs
+        } = input;
+
+        if (recommendedProductIDs.length === 0) {
+            return {
+                message: "recommendedProductIDs-cannot-be-empty",
+                code: 400
+            };
+        }
+
+        const objectIdArray = recommendedProductIDs.map(id => new mongoose.Types.ObjectId(id));
+
+        const result = await RecommendedProduct.deleteMany({
+            _id: {
+                $in: objectIdArray
+            },
+            userID: userID
+        });
+
+        if (result.deletedCount === 0) {
+            return {
+                message: "recommended-product-not-found-or-unauthorized",
+                code: 404
+            };
+        }
+
+        return {
+            message: "recommended-product-deleted-successfully",
+            code: 200
+        };
+
+    } catch (error) {
+        console.error("DeleteRecommendedProduct action error:", error);
+        return {
+            message: "internal-server-error",
+            code: 500
+        };
+    }
+};
+
+export default deleteRecommendedProduct;

+ 17 - 0
src/actions/menu/deleteRecommendedProduct/types.ts

@@ -0,0 +1,17 @@
+import {
+    IsNotEmpty,
+    IsString,
+    IsArray
+} from "class-validator";
+
+export class DeleteRecommendedProductInput {
+    @IsArray({ message: "recommendedProductIDs-must-be-an-array" })
+    @IsString({ each: true, message: "each-recommendedProductID-must-be-a-string" })
+    @IsNotEmpty({ message: "recommendedProductIDs-is-required" })
+    recommendedProductIDs!: string[];
+}
+
+export type DeleteRecommendedProductResult = {
+    message: string;
+    code: number;
+};

+ 97 - 0
src/actions/menu/getRecommendedProducts/index.ts

@@ -0,0 +1,97 @@
+import mongoose from "mongoose";
+import {
+    RecommendedProduct
+} from "../../../models";
+import {
+    GetRecommendedProductsResult,
+    GetRecommendedProductsInput
+} from "./types";
+
+const getRecommendedProducts = async (userID: string, query: GetRecommendedProductsInput): Promise<GetRecommendedProductsResult> => {
+    try {
+        const {
+            isActive,
+            search
+        } = query;
+
+        const matchStage: any = {
+            userID: new mongoose.Types.ObjectId(userID)
+        };
+
+        if (isActive !== undefined) {
+            matchStage.isActive = isActive === "true";
+        }
+
+        if (search) {
+            matchStage.$or = [
+                {
+                    title: {
+                        $regex: search,
+                        $options: "i"
+                    }
+                },
+                {
+                    description: {
+                        $regex: search,
+                        $options: "i"
+                    }
+                }
+            ];
+        }
+
+        const recommendedProducts = await RecommendedProduct.aggregate([
+            {
+                $match: matchStage
+            },
+            {
+                $lookup: {
+                    from: "products",
+                    localField: "productID",
+                    foreignField: "_id",
+                    as: "productDetails"
+                }
+            },
+            {
+                $unwind: {
+                    path: "$productDetails",
+                    preserveNullAndEmptyArrays: true
+                }
+            },
+            {
+                $sort: {
+                    delay: 1
+                }
+            }
+        ]);
+
+        const payload = recommendedProducts.map((rec) => ({
+            _id: rec._id.toString(),
+            productID: rec.productID.toString(),
+            title: rec.title,
+            description: rec.description,
+            delay: rec.delay,
+            isActive: rec.isActive,
+            productDetails: rec.productDetails ? {
+                price: rec.productDetails.price,
+                photos: rec.productDetails.photos,
+                isAvailable: rec.productDetails.isAvailable,
+                originalTitle: rec.productDetails.title
+            } : null
+        }));
+
+        return {
+            message: "recommended-products-retrieved-successfully",
+            code: 200,
+            payload: payload
+        };
+
+    } catch (error) {
+        console.error("GetRecommendedProducts error:", error);
+        return {
+            message: "internal-server-error",
+            code: 500
+        };
+    }
+};
+
+export default getRecommendedProducts;

+ 21 - 0
src/actions/menu/getRecommendedProducts/types.ts

@@ -0,0 +1,21 @@
+import {
+    IsBooleanString,
+    IsOptional,
+    IsString
+} from "class-validator";
+
+export class GetRecommendedProductsInput {
+    @IsBooleanString({ message: "isActive-must-be-a-boolean-string" })
+    @IsOptional()
+    isActive?: string;
+
+    @IsString()
+    @IsOptional()
+    search?: string;
+}
+
+export type GetRecommendedProductsResult = {
+    message: string;
+    code: number;
+    payload?: any;
+};

+ 13 - 0
src/actions/menu/index.ts

@@ -36,3 +36,16 @@ export {
 export {
     default as updateMenu
 } from "./updateMenu";
+
+export {
+    default as addRecommendedProduct
+} from "./addRecommendedProduct";
+export {
+    default as deleteRecommendedProduct
+} from "./deleteRecommendedProduct";
+export {
+    default as getRecommendedProducts
+} from "./getRecommendedProducts";
+export {
+    default as updateRecommendedProduct
+} from "./updateRecommendedProduct";

+ 10 - 0
src/actions/menu/types/index.ts

@@ -27,3 +27,13 @@ export {
 export {
     DeleteMenuInput
 } from "../deleteMenu/types";
+
+export {
+    AddRecommendedProductInput
+} from "../addRecommendedProduct/types";
+export {
+    UpdateRecommendedProductInput
+} from "../updateRecommendedProduct/types";
+export {
+    DeleteRecommendedProductInput
+} from "../deleteRecommendedProduct/types";

+ 19 - 22
src/actions/menu/updateCategory/index.ts

@@ -1,9 +1,10 @@
+import mongoose from "mongoose";
 import {
-    Category 
+    Category
 } from "../../../models";
 import {
-    UpdateCategoryInput, 
-    UpdateCategoryResult 
+    UpdateCategoryInput,
+    UpdateCategoryResult
 } from "./types";
 
 const updateCategory = async (userID: string, input: UpdateCategoryInput): Promise<UpdateCategoryResult> => {
@@ -13,34 +14,30 @@ const updateCategory = async (userID: string, input: UpdateCategoryInput): Promi
             ...updateFields
         } = input;
 
-        const category = await Category.findOne({
-            _id: categoryID,
-            userID
-        });
+        const objectCategoryID = new mongoose.Types.ObjectId(categoryID);
+        const objectUserID = new mongoose.Types.ObjectId(userID);
 
-        if (!category) {
-            return {
-                message: "category-not-found",
-                code: 404
-            };
-        }
-
-        const updatedCategory = await Category.findByIdAndUpdate(
-            categoryID,
-            updateFields,
+        const updatedCategory = await Category.findOneAndUpdate(
+            {
+                _id: objectCategoryID,
+                userID: objectUserID
+            },
+            {
+                $set: updateFields
+            },
             {
                 new: true
             }
         );
-        
-        if (!updatedCategory){
+
+        if (!updatedCategory) {
             return {
-                message: "category-not-found",
+                message: "category-not-found-or-unauthorized",
                 code: 404
             };
         }
 
-        const payload={
+        const payload = {
             category: {
                 _id: updatedCategory._id.toString(),
                 title: updatedCategory.title,
@@ -57,7 +54,7 @@ const updateCategory = async (userID: string, input: UpdateCategoryInput): Promi
         console.error("UpdateCategory error:", error);
         return {
             message: "internal-server-error",
-            code: 500 
+            code: 500
         };
     }
 };

+ 28 - 2
src/actions/menu/updateMenu/index.ts

@@ -1,3 +1,4 @@
+import mongoose from "mongoose";
 import {
     Menu
 } from "../../../models";
@@ -13,10 +14,35 @@ const updateMenu = async (userID: string, input: UpdateMenuInput): Promise<Updat
             ...updateFields
         } = input;
 
+        const objectMenuID = new mongoose.Types.ObjectId(menuID);
+        const objectUserID = new mongoose.Types.ObjectId(userID);
+
+        if (updateFields.isActive === false) {
+
+            const activeMenuCount = await Menu.countDocuments({
+                userID: objectUserID,
+                isActive: true
+            });
+
+            if (activeMenuCount === 1) {
+                const isThisTheActiveOne = await Menu.findOne({
+                    _id: objectMenuID,
+                    isActive: true
+                });
+
+                if (isThisTheActiveOne) {
+                    return {
+                        message: "cannot-deactivate-last-active-menu",
+                        code: 400
+                    };
+                }
+            }
+        }
+
         const updatedMenu = await Menu.findOneAndUpdate(
             {
-                _id: menuID,
-                userID
+                _id: objectMenuID,
+                userID: objectUserID
             },
             {
                 $set: updateFields

+ 26 - 5
src/actions/menu/updateProduct/index.ts

@@ -1,6 +1,7 @@
 import mongoose from "mongoose";
 import {
-    Product
+    Product,
+    Menu
 } from "../../../models";
 import {
     UpdateProductResult,
@@ -15,18 +16,24 @@ const updateProduct = async (userID: string, input: UpdateProductInput): Promise
             ...rest
         } = input;
 
+        const objectProductID = new mongoose.Types.ObjectId(productID);
+        const objectUserID = new mongoose.Types.ObjectId(userID);
+
         const updateData: any = {
             ...rest
         };
 
+        let objectCategoryIDs: mongoose.Types.ObjectId[] | undefined;
+
         if (categoryIDs !== undefined) {
-            updateData.categoryIDs = categoryIDs;
+            objectCategoryIDs = categoryIDs.map(id => new mongoose.Types.ObjectId(id));
+            updateData.categoryIDs = objectCategoryIDs;
         }
 
         const updatedDoc = await Product.findOneAndUpdate(
             {
-                _id: new mongoose.Types.ObjectId(productID),
-                userID: new mongoose.Types.ObjectId(userID)
+                _id: objectProductID,
+                userID: objectUserID
             },
             {
                 $set: updateData
@@ -43,10 +50,24 @@ const updateProduct = async (userID: string, input: UpdateProductInput): Promise
             };
         }
 
+        if (objectCategoryIDs !== undefined) {
+            await Menu.updateMany(
+                {
+                    userID: objectUserID,
+                    "products.productID": objectProductID
+                },
+                {
+                    $set: {
+                        "products.$.categoryIDs": objectCategoryIDs
+                    }
+                }
+            );
+        }
+
         const aggregatedProduct = await Product.aggregate([
             {
                 $match: {
-                    _id: new mongoose.Types.ObjectId(productID)
+                    _id: objectProductID
                 }
             },
             {

+ 91 - 0
src/actions/menu/updateRecommendedProduct/index.ts

@@ -0,0 +1,91 @@
+import mongoose from "mongoose";
+import {
+    RecommendedProduct
+} from "../../../models";
+import {
+    UpdateRecommendedProductResult,
+    UpdateRecommendedProductInput
+} from "./types";
+
+const updateRecommendedProduct = async (userID: string, input: UpdateRecommendedProductInput): Promise<UpdateRecommendedProductResult> => {
+    try {
+        const {
+            recommendedProductID,
+            ...updateFields
+        } = input;
+
+        const updatedDoc = await RecommendedProduct.findOneAndUpdate(
+            {
+                _id: new mongoose.Types.ObjectId(recommendedProductID),
+                userID: new mongoose.Types.ObjectId(userID)
+            },
+            {
+                $set: updateFields
+            },
+            {
+                new: true
+            }
+        );
+
+        if (!updatedDoc) {
+            return {
+                message: "recommended-product-not-found-or-unauthorized",
+                code: 404
+            };
+        }
+
+        const aggregatedRecommendedProduct = await RecommendedProduct.aggregate([
+            {
+                $match: {
+                    _id: updatedDoc._id
+                }
+            },
+            {
+                $lookup: {
+                    from: "products",
+                    localField: "productID",
+                    foreignField: "_id",
+                    as: "productDetails"
+                }
+            },
+            {
+                $unwind: {
+                    path: "$productDetails",
+                    preserveNullAndEmptyArrays: true
+                }
+            }
+        ]);
+
+        const formattedPayload = aggregatedRecommendedProduct[0];
+
+        const payload = {
+            _id: formattedPayload._id.toString(),
+            productID: formattedPayload.productID.toString(),
+            title: formattedPayload.title,
+            description: formattedPayload.description,
+            delay: formattedPayload.delay,
+            isActive: formattedPayload.isActive,
+            productDetails: formattedPayload.productDetails ? {
+                price: formattedPayload.productDetails.price,
+                photos: formattedPayload.productDetails.photos,
+                isAvailable: formattedPayload.productDetails.isAvailable,
+                originalTitle: formattedPayload.productDetails.title
+            } : null
+        };
+
+        return {
+            message: "recommended-product-updated-successfully",
+            code: 200,
+            payload: payload
+        };
+
+    } catch (error) {
+        console.error("UpdateRecommendedProduct action error:", error);
+        return {
+            message: "internal-server-error",
+            code: 500
+        };
+    }
+};
+
+export default updateRecommendedProduct;

+ 37 - 0
src/actions/menu/updateRecommendedProduct/types.ts

@@ -0,0 +1,37 @@
+import {
+    IsNotEmpty,
+    IsOptional,
+    IsBoolean,
+    IsString,
+    IsNumber,
+    Min
+} from "class-validator";
+
+export class UpdateRecommendedProductInput {
+    @IsString({ message: "recommendedProductID-must-be-a-string" })
+    @IsNotEmpty({ message: "recommendedProductID-is-required" })
+    recommendedProductID!: string;
+
+    @IsString({ message: "title-must-be-a-string" })
+    @IsOptional()
+    title?: string;
+
+    @IsString({ message: "description-must-be-a-string" })
+    @IsOptional()
+    description?: string;
+
+    @Min(0, { message: "delay-cannot-be-negative" })
+    @IsNumber({}, { message: "delay-must-be-a-number" })
+    @IsOptional()
+    delay?: number;
+
+    @IsBoolean({ message: "is-active-must-be-a-boolean" })
+    @IsOptional()
+    isActive?: boolean;
+}
+
+export type UpdateRecommendedProductResult = {
+    message: string;
+    code: number;
+    payload?: any;
+};

+ 56 - 0
src/controllers/menuController.ts

@@ -5,6 +5,10 @@ import {
     AuthRequest
 } from "../middlewares/authMiddleware";
 import {
+    deleteRecommendedProduct as _deleteRecommendedProduct,
+    updateRecommendedProduct as _updateRecommendedProduct,
+    getRecommendedProducts as _getRecommendedProducts,
+    addRecommendedProduct as _addRecommendedProduct,
     deleteCategory as _deleteCategory,
     updateCategory as _updateCategory,
     deleteProduct as _deleteProduct,
@@ -176,4 +180,56 @@ export const updateMenu = async (req: AuthRequest, res: Response): Promise<void>
             })
         });
 };
+//#endregion
+
+//#region Recommended Product Controllers
+export const addRecommendedProduct = async (req: AuthRequest, res: Response): Promise<void> => {
+    const recommendLimit = req.context!.planDetails?.recommendedProductLimit ?? 0;
+    const result = await _addRecommendedProduct(req.context!.userID, recommendLimit, req.body);
+
+    res.status(result.code)
+        .json({
+            message: result.message,
+            code: result.code,
+            ...(result.payload && {
+                payload: result.payload
+            })
+        });
+};
+
+export const getRecommendedProducts = async (req: AuthRequest, res: Response): Promise<void> => {
+    const result = await _getRecommendedProducts(req.context!.userID, req.query);
+
+    res.status(result.code)
+        .json({
+            message: result.message,
+            code: result.code,
+            ...(result.payload && {
+                payload: result.payload
+            })
+        });
+};
+
+export const deleteRecommendedProduct = async (req: AuthRequest, res: Response): Promise<void> => {
+    const result = await _deleteRecommendedProduct(req.context!.userID, req.body);
+
+    res.status(result.code)
+        .json({
+            message: result.message,
+            code: result.code
+        });
+};
+
+export const updateRecommendedProduct = async (req: AuthRequest, res: Response): Promise<void> => {
+    const result = await _updateRecommendedProduct(req.context!.userID, req.body);
+
+    res.status(result.code)
+        .json({
+            message: result.message,
+            code: result.code,
+            ...(result.payload && {
+                payload: result.payload
+            })
+        });
+};
 //#endregion

+ 1 - 1
src/models/Category.ts

@@ -1,6 +1,6 @@
 import mongoose, {
     Document,
-    Schema 
+    Schema
 } from "mongoose";
 
 export interface ICategory extends Document {

+ 0 - 2
src/models/Menu.ts

@@ -22,7 +22,6 @@ const menuSchema = new Schema<IMenu>(
     {
         userID: {
             type: Schema.Types.ObjectId,
-            ref: "User", // TODO aggregate fonk kullanılacak ref değil
         },
         title: {
             type: String,
@@ -46,7 +45,6 @@ const menuSchema = new Schema<IMenu>(
                 },
                 productID: {
                     type: Schema.Types.ObjectId,
-                    ref: "Product", // TODO ilerde product servis buna göre düzenlenecek aggregate fonskiyonu kullanılacak ref değil
                 },
                 isViewPrice: {
                     type: Boolean,

+ 2 - 3
src/models/RecommendedProduct.ts

@@ -1,5 +1,6 @@
 import mongoose, {
-    Document, Schema
+    Document,
+    Schema
 } from "mongoose";
 
 export interface IRecommendedProduct extends Document {
@@ -17,11 +18,9 @@ const recommendedProductSchema = new Schema<IRecommendedProduct>(
     {
         userID: {
             type: Schema.Types.ObjectId,
-            ref: "User", // TODO aggregate fonk kullanılacak ref değil
         },
         productID: {
             type: Schema.Types.ObjectId,
-            ref: "Product", // TODO aggregate fonk kullanılacak ref değil
         },
         title: {
             type: String,

+ 4 - 3
src/models/Subscription.ts

@@ -1,5 +1,6 @@
 import mongoose, {
-    Schema, Document 
+    Document,
+    Schema
 } from "mongoose";
 
 export interface ISubscription extends Document {
@@ -35,7 +36,7 @@ const SubscriptionSchema = new Schema<ISubscription>(
             type: Date
         },
         status: {
-            enum: ["active", "expired"], 
+            enum: ["active", "expired"],
             default: "active",
             type: String
         },
@@ -44,7 +45,7 @@ const SubscriptionSchema = new Schema<ISubscription>(
         },
     },
     {
-        timestamps: true 
+        timestamps: true
     }
 );
 

+ 20 - 2
src/routes/menuRoutes.ts

@@ -2,6 +2,10 @@ import {
     Router
 } from "express";
 import {
+    deleteRecommendedProduct,
+    updateRecommendedProduct,
+    getRecommendedProducts,
+    addRecommendedProduct,
     deleteCategory,
     updateCategory,
     getCategories,
@@ -12,10 +16,14 @@ import {
     addProduct,
     deleteMenu,
     updateMenu,
-    addMenu,
-    getMenus
+    getMenus,
+    addMenu
 } from "../controllers/menuController";
+
 import {
+    DeleteRecommendedProductInput,
+    UpdateRecommendedProductInput,
+    AddRecommendedProductInput,
     UpdateCategoryInput,
     DeleteCategoryInput,
     UpdateProductInput,
@@ -26,6 +34,7 @@ import {
     DeleteMenuInput,
     AddMenuInput
 } from "../actions/menu/types";
+
 import {
     authMiddleware
 } from "../middlewares/authMiddleware";
@@ -35,19 +44,28 @@ import {
 
 const router = Router();
 
+// --- KATEGORİ ENDPOINTLERİ ---
 router.delete("/deleteCategory", authMiddleware, validateBody(DeleteCategoryInput), deleteCategory);
 router.put("/updateCategory", authMiddleware, validateBody(UpdateCategoryInput), updateCategory);
 router.post("/addCategory", authMiddleware, validateBody(AddCategoryInput), addCategory);
 router.get("/getCategories", authMiddleware, getCategories);
 
+// --- ÜRÜN ENDPOINTLERİ ---
 router.delete("/deleteProduct", authMiddleware, validateBody(DeleteProductInput), deleteProduct);
 router.put("/updateProduct", authMiddleware, validateBody(UpdateProductInput), updateProduct);
 router.post("/addProduct", authMiddleware, validateBody(AddProductInput), addProduct);
 router.get("/getProducts", authMiddleware, getProducts);
 
+// --- MENÜ ENDPOINTLERİ ---
 router.delete("/deleteMenu", authMiddleware, validateBody(DeleteMenuInput), deleteMenu);
 router.put("/updateMenu", authMiddleware, validateBody(UpdateMenuInput), updateMenu);
 router.post("/addMenu", authMiddleware, validateBody(AddMenuInput), addMenu);
 router.get("/getMenus", authMiddleware, getMenus);
 
+// --- ÖNE ÇIKAN ÜRÜN ENDPOINTLERİ ---
+router.delete("/deleteRecommendedProduct", authMiddleware, validateBody(DeleteRecommendedProductInput), deleteRecommendedProduct);
+router.put("/updateRecommendedProduct", authMiddleware, validateBody(UpdateRecommendedProductInput), updateRecommendedProduct);
+router.post("/addRecommendedProduct", authMiddleware, validateBody(AddRecommendedProductInput), addRecommendedProduct);
+router.get("/getRecommendedProducts", authMiddleware, getRecommendedProducts);
+
 export default router;