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

السؤال

نشر

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

أواجه مشكل في رفع التطبيق التالي على 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
نشر
بتاريخ 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.

  • إعلانات

  • تابعنا على



×
×
  • أضف...