اذهب إلى المحتوى

السؤال

نشر

السلام عليكم.

أواجه مشكل في رفع التطبيق التالي على render.

package.json

{
  "name": "chat-app-backend",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node dist/index.js",
    "server": "tsx watch index.ts",
    "build": "prisma generate && tsc",
    "postinstall": "prisma generate"
  },
  "author": "Mahmoud Saadaoui",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@prisma/client": "^6.6.0",
    "bcrypt": "^5.1.1",
    "cloudinary": "^2.6.1",
    "cors": "^2.8.5",
    "dotenv": "^16.5.0",
    "express": "^4.21.2",
    "express-async-errors": "^3.1.1",
    "jsonwebtoken": "^9.0.2",
    "multer": "^2.0.0",
    "nodemailer": "^7.0.3",
    "socket.io": "^4.8.1",
    "zod": "^3.24.4"
  },
  "devDependencies": {
    "@types/bcrypt": "^5.0.2",
    "@types/express": "^5.0.1",
    "@types/jsonwebtoken": "^9.0.9",
    "@types/multer": "^1.4.12",
    "@types/node": "^22.16.0",
    "@types/nodemailer": "^6.4.17",
    "@types/web-push": "^3.6.4",
    "prisma": "^6.6.0",
    "ts-node": "^10.9.2",
    "tsx": "^4.19.3",
    "typescript": "^5.8.3"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist",
    "target": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    // "module": "commonjs",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "incremental": true,
    "skipLibCheck": true
  },
  "include": ["index.ts", "**/*.ts"],
  "exclude": ["node_modules"]
}

تنظيم الملفات

Capture.JPG.cc01474cb93629ba26fc09c42f0bcdf6.JPG

الخطأ

اقتباس

node:internal/modules/esm/resolve:275

Jul 9 08:17:22 AM

throw new ERR_MODULE_NOT_FOUND(

Jul 9 08:17:22 AM

^

Jul 9 08:17:22 AM

Jul 9 08:17:22 AM

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/opt/render/project/src/backend/dist/routes/userRoute' imported from /opt/render/project/src/backend/dist/index.js

Jul 9 08:17:22 AM

at finalizeResolution (node:internal/modules/esm/resolve:275:11)

Jul 9 08:17:22 AM

at moduleResolve (node:internal/modules/esm/resolve:860:10)

Jul 9 08:17:22 AM

at defaultResolve (node:internal/modules/esm/resolve:984:11)

Jul 9 08:17:22 AM

at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:780:12)

Jul 9 08:17:22 AM

at #cachedDefaultResolve (node:internal/modules/esm/loader:704:25)

Jul 9 08:17:22 AM

at ModuleLoader.resolve (node:internal/modules/esm/loader:687:38)

Jul 9 08:17:22 AM

at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:305:38)

Jul 9 08:17:22 AM

at ModuleJob._link (node:internal/modules/esm/module_job:137:49) {

Jul 9 08:17:22 AM

code: 'ERR_MODULE_NOT_FOUND',

Jul 9 08:17:22 AM

url: 'file:///opt/render/project/src/backend/dist/routes/userRoute'

Jul 9 08:17:22 AM

}

Jul 9 08:17:22 AM

Jul 9 08:17:22 AM

Node.js v22.16.0

ملاحظة

عندما أقوم بـ

اقتباس

npm run build

  يظهر بوضوح الملف

اقتباس

backend/dist/controllers/userRoute.js

شكرا لكم.

Recommended Posts

  • 0
نشر

بسبب "type": "module" في package.json، والتي تفرض عليك تضمين امتداد الملف .js في جميع عبارات الاستيراد، أي عليك تحديث جميع عبارات الاستيراد في مشروعك لتتضمن .js

 

  • 0
نشر
بتاريخ 1 ساعة قال Mustafa Suleiman:

بسبب "type": "module" في package.json، والتي تفرض عليك تضمين امتداد الملف .js في جميع عبارات الاستيراد، أي عليك تحديث جميع عبارات الاستيراد في مشروعك لتتضمن .js

 

شكرا.

المشكل الحالي:
بعد رفع المشروع إلى البيئة الإنتاجية، لم أعد قادرًا على تغيير صورة الملف الشخصي للمستخدم، بالرغم من أن العملية تعمل بشكل سليم تمامًا في بيئة التطوير (development).

✅ لا تظهر أي أخطاء سواء في الواجهة الأمامية (frontend) أو الخلفية (backend).
✅ نفس الكود المستخدم في التطوير تم رفعه كما هو إلى الإنتاج.

 

الكود كاملا

الواجهة الخلفية:

profile.ts 
/**
 *  @method  PUT
 *  @route   /api/profile/:id/profile-photo
 *  @desc    Update user profile (name & bio)
 *  @access  private (only user himself)
*/
export const updateProfilePhoto = async (
  req: Request,
  res: Response
): Promise<void> => { 
  try { 
    // Check if the user is authorized to update this profile
    if (req.user.id !== req.params.id) {
      res.status(403).json({ message: "Unauthorized: You are not allowed to update this profile." });
      return
    }
    // Check if an image file was uploaded
    if (!req.file) {
      res.status(400).json({ message: "No image file uploaded." })
      return
    } 
    // Build the local path to the uploaded image
    const imagePath = path.join(__dirname, ../images/${req.file.filename})
    // Upload the image to Cloudinary
    const uploadResult = await cloudinaryUploadImage(imagePath)
    // Remove the local image file after upload
    await fs.unlink(imagePath);
    // Retrieve the user from the database
    const profile = await prisma.user.findUnique({ where: { id: req.user.id } }) 
    if (!profile) {
      res.status(404).json({ message: "User not found" });
      return
    }
    // If the user already has a profile picture, remove it from Cloudinary
    const profilePicture = profile.profilePicture as { publicId: string; secureUrl: string };
    if (profilePicture?.publicId) {
      await cloudinaryRemoveImage(profilePicture.publicId);
    }
    // Update the user's profile with the new profile picture
    const updatedProfilePhoto = await prisma.user.update({
      where: { id: req.user.id },
      data: {
        profilePicture: {
          publicId: uploadResult.public_id,
          secureUrl: uploadResult.secure_url
        }
      },
      select: {
        id: true,
        name: true,
        profilePicture: true
      }
    });
    // Return a success response with updated user info
    res.status(200).json({
      updatedProfilePhoto,
      message: "Profile photo updated successfully."
    });
  } catch (err) {
    handleError(res, err as Error);
  }
};
middlewares/cloudinary.ts
***************************

import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET
})

export const cloudinaryUploadImage = async(fileToUpload: string) => {
  try {
      const data = await cloudinary.uploader.upload(fileToUpload, {
          resource_type: 'auto',   
      })
      return data
  } catch (error) {
      console.log(error)
      throw new Error('Internal Server Error (cloudinary)')
  }
}

export const cloudinaryRemoveImage = async(imagePublicId: string ) => {
  try {
      const result = await cloudinary.uploader.destroy(imagePublicId)
      return result
  } catch (error) {
      console.log(error)
      throw new Error('Internal Server Error (cloudinary)')
  }
}
middlewares/upload.ts 
*********************

import path from "path";
import multer from "multer";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Photo Storage
const photoStorage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, path.join(__dirname, "../images"));
  },
  filename: function (req, file, cb) {
    if (file) {
      cb(null, new Date().toISOString().replace(/:/g, "-") + file.originalname);
    } else {
      cb(new Error("File upload error"), "")
    }
  },
});

// Photo Upload Middleware
export const photoUpload = multer({
  storage: photoStorage,
  fileFilter: function(req, file, cb) {
      if (file.mimetype.startsWith("image")) {
          cb(null, true)
      } else {
          cb(new Error("Unsupported File Format"))
      }
  },
  limits: { fieldSize: 1024 * 1024 * 5 }
})

الواجهة الأمامية

EditProfileImage.tsx 
*********************

import { FaCamera } from "react-icons/fa";
import { useUpdateProfilePhoto } from "../../hooks/useProfile";
import { useAuthStore } from "../../store/auth.store";
import { toast } from "react-toastify";
import Spinner from "../Spinner";
import { defaultAvatar } from "../../utils/constantes";

const EditProfileImage = () => {
  const { user, token, setUser } = useAuthStore();
  const { mutate, isPending } = useUpdateProfilePhoto();
  
  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files?.[0]) {
      updateProfilePhoto(e.target.files[0]);
    }
  };

  const updateProfilePhoto = (selectedFile: File) => {
    if (!token || !user) return
    mutate(
      {file: selectedFile, token, id:user?.id}, {
      onSuccess: (data) => {
        setUser({
          ...user,
          photoProfile: data?.updatedProfilePhoto.profilePicture.secureUrl,
        });
        toast.success(data?.message)
      },
      onError: (err) => {
        console.log(err)
        toast.error('An unexpected error occurred.')
      }
    })
  };

  if (isPending) {
    return (
      <Spinner />
    );
  }
  return (
    .......
  );
};

export default EditProfileImage;
    
******************************
hooks/useUpdateProfilePhoto 
******************************
 export const useUpdateProfilePhoto = () => {
  return useMutation({
    mutationFn: ({
      file,
      token,
      id,
    }: {
      file: File;
      token: string;
      id: string;
    }) => updateProfilePhotoApi(file, token, id),
  });
}; 

************************
api/profile.api.ts 
***********************
export const updateProfilePhotoApi = async (
  file: File,
  token: string,
  id: string
) => {
  try {
    const formData = new FormData()
    formData.append("image", file)
    const res = await axios.put(
      ${process.env.REACT_APP_API_URL}/profile/${id}/profile-photo,
      formData,
      {
        headers: {
          Authorization: Bearer ${token},
        },
        withCredentials: true,
      }
    );
    return res.data
  } catch (error) {
    console.log(error)
  }
};

 

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...