1
votes

I'm building a website that users can upload insights and comments on reading novels, and users are free to fetch images of the novel or not.

If an image is chosen, the post schema has image & imageId attribute for Cloudinary uploads and future manipulation such as changing(update route) or deleting(destroy route) it from the Cloudinary library. If no image is chosen, then a default image takes place.

Problem is, I managed to make default image function work, however I don't want multiple same default images uploaded to Cloudinary library, so I put default_image.jpg in local server folder (/public/images) to be exact), and this default_image.jpg shouldn't be in Cloudinary library and this should save a lot of capacity.

However, for these posts without a chosen image, I dunno what I should assign for their imageId property.

I tried undefined and null, and of course, they didn't work, cuz' this way it won't be able to find certain novel.imageId if they're all undefined or null.

// Schema for data
var fictionSchema = new mongoose.Schema({
    ...
    ...
    image: String,
    // for cloudinary API to track which image to delete or update
    imageId: String,
    ...
    ...
});
// CREATE route
router.post("/", middleware.isLoggedIn, upload.single('image'), (req, res) => {
    if (req.file){
        // req.file.path comes from multer npm
        cloudinary.v2.uploader.upload(req.file.path, function (err, result) {
            if (err) {
                ...
            }
            // add cloudinary url for the image to the novel object under image property
            req.body.novel.image = result.secure_url;
            req.body.novel.imageId = result.public_id;
        });
    } else {
        // setup default image for new novels
        req.body.novel.image = "/images/noimage.jpg";

        // imageId here should be ... empty? Undefined? Null? A fixed ID but may be delete when accessing Destroy route?
        req.body.novel.imageId = undefined;
    }
    ...
    ...
    Novel.create(req.body.novel, function (err, newlyAddedNovel) {
        if (err) {
            req.flash('error', err.message);
            ...
        } else {
            req.flash("success", "Novel added successfully.");
            ...
        }
    });
});
// DESTROY route
router.delete("/:id", middleware.checkNovelOwnership, (req, res) => {
    // find and delete the correct novel along with the image on cloudinary
    Novel.findById(req.params.id, async (err, novel) => {
        if (err) {
            req.flash("error", err.message);
            return res.redirect("back");
        }
        try {
            await cloudinary.v2.uploader.destroy(novel.imageId);
            // delete the novel found
            novel.remove();
            // delete the image from cloudinary
            req.flash("success", "Novel deleted successfully.");
            ...
            ...
        } catch (err) {
            ..
        }
    });
});
2

2 Answers

0
votes

Good news, I solved the problem! Her-ray! Took me around 2 hrs though...

So, in the end everything worked just as I wanted, such finesse.

Codes are as below:

• CREATE route

For new posts, I set up every new novel with the default image first, then if there's an image (req.file) given, change it and upload.

Afterwards, remember to create that novel data in mongo database. (Novel.create() in my case.)

router.post("/", middleware.isLoggedIn, upload.single('image'), async function (req, res) {
    // set every new novel with default image first
    req.body.novel.image = "https://res.cloudinary.com/dvfkbz6la/image/upload/v1565434656/noimage_ew1uri.jpg";
    req.body.novel.imageId = "noimage_ew1uri";
    req.body.novel.user = {
        id: req.user._id,
        username: req.user.username
    };

    if (req.file) {
        // req.file.path comes from multer npm
        await cloudinary.v2.uploader.upload(req.file.path, function (err, result) {
            if (err) {
                req.flash('error', err.message);
                return res.redirect('back');
            }
            // add cloudinary url for the image to the novel object under image property
            // add image's public_id (imageId in novel model) to novel object
            req.body.novel.image = result.secure_url;
            req.body.novel.imageId = result.public_id;
        });
    }
    Novel.create(req.body.novel, function (err, newlyAddedNovel) {
        if (err) {
            ...
        } else {
            ...
        }
    });
});

• UPDATE route

In try block, if (novel.imageId = "DEFAULT_IMAGEID") { } else { } is added.

// UPDATE route
router.put("/:id", middleware.checkNovelOwnership, upload.single('image'), (req, res) => {
    // find the correct novel
    Novel.findById(req.params.id, async (err, novel) => {
        if (err) {
            ...
        } else {
            // if there's a req.file, we know user is trying to upload a new image
            if (req.file) {
                try {
                    // if imageId is default, await the result to be uploaded
                    if (novel.imageId = "noimage_ew1uri") {
                        var result = await cloudinary.v2.uploader.upload(req.file.path);
                    } else {
                        //  if not default, find the old image using imageId and delete
                        await cloudinary.v2.uploader.destroy(novel.imageId);
                        var result = await cloudinary.v2.uploader.upload(req.file.path);
                    }
                    novel.imageId = result.public_id;
                    novel.image = result.secure_url;
                } catch (err) {
                    ...
                }
            }
            novel.title = req.body.title;
            novel.author = req.body.author;
            novel.price = req.body.price;
            novel.description = req.body.description;

            // remember to save the changed novel
            novel.save();
            req.flash("success", "Successfully Updated!");
            res.redirect("/novels/" + novel._id);
        }
    });
});

• DESTROY route

In try block, if (novel.imageId != "DEFAULT_IMAGEID") { } else { } is added.

// DESTROY route
router.delete("/:id", middleware.checkNovelOwnership, (req, res) => {
    // find and delete the correct novel along with the image on cloudinary
    Novel.findById(req.params.id, async (err, novel) => {
        if (err) {
            req.flash("error", err.message);
            return res.redirect("back");
        }
        try {
            // if novel.imageId isn't default, destroy it from Cloudinary
            if (novel.imageId != "noimage_ew1uri") {
                await cloudinary.v2.uploader.destroy(novel.imageId);
            }
            // delete the novel found
            novel.remove();
            ...
        } catch (err) {
            ...
        }
    });
});

Only problem left is, I dunno why but when hitting UPDATE route, and when the imageId isn't the default one and the user changed the image,

the old image will NOT be destroyed and stayed in Cloudinary library. Such oddness.

I sure have this code set up

Novel.findById(req.params.id, async (err, novel) => {
        if (err) {
            ...
        } else {
            if (req.file) {
                try {
                    // if imageId is default, await the result to be uploaded
                    if (novel.imageId = "noimage_ew1uri") {
                        ...
                    } else {
                        //  if not default, find the old image using imageId and delete
                        await cloudinary.v2.uploader.destroy(novel.imageId);
                        var result = await cloudinary.v2.uploader.upload(req.file.path);
                    }
...
...

Dunno why await cloudinary.v2.uploader.destroy(novel.imageId); isn't working as expected. Hmmm...

0
votes

Images that were already available in your site, are cached in one or more CDN edges. Therefore, even if you delete the image from your Cloudinary account, cached copies might still be available. In default, all delivered images are cached for 30 days.

When using the destroy API with the invalidate parameter set to true, the image will be deleted and purged from the CDN, allowing for the new image to be displayed.

Alternatively, when updating, you can accomplish the same result by setting the overwrite and invalidate parameters to true when uploading the new file (without the need to call the destroy method).