Search code examples
node.jsreactjsexpressmongooseredux

How to create public shareable link for private user resources in MERN application


I am creating a movie web application using MERN stack where a user can sign up and login. Users can also add the movies they like to their respective list.

Now, I want to add a feature where the user can share their created list with anyone through a shareable link just like how we give access to files on google drive without the person requiring to login.

Please give me some insights on how this can be achieved.

Model of the database I want to share.

const mongoose = require("mongoose");

const FavoritesSchema = mongoose.Schema({
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "user",
    },
    imdbID: {
        type: String,
        require: true,
    },
    Poster: {
        type: String,
        require: true,
    },
    Title: {
        type: String,
        require: true,
    },
    Type: {
        type: String,
        require: true,
    },
    Year: {
        type: String,
        require: true,
    },
    date: {
        type: Date,
        default: Date.now,
    },
});

module.exports = mongoose.model("favorites", FavoritesSchema);

server.js

const express = require("express");
const dotenv = require("dotenv");
const connectDB = require("./config/db");
const path = require("path");

const cors = require("cors");
const axios = require("axios");

dotenv.config();

const app = express();

connectDB();
app.use(cors());

app.all("*", (req, res, next) => {
    res.header("Access-Control-Allow-Origin", "https://localhost:3000");
    next();
});

app.use(express.json({ extended: false }));

app.use("/api/users", require("./routes/users"));
app.use("/api/auth", require("./routes/auth"));
app.use("/api/mylist", require("./routes/mylist"));

app.get("/api/data/:searchValue", async (req, res) => {
    const { searchValue } = req.params;
    console.log(searchValue);
    const response = await axios.get(
        `http://www.omdbapi.com/?apikey=${process.env.REACT_APP_CLIENT_SECRET}&s=${searchValue}`
    );
    console.log(response.data);
    return res.send(response.data);
});

app.get("/api/data/:searchValue/:currentPage", async (req, res) => {
    const { searchValue, currentPage } = req.params;
    console.log(searchValue);
    const response = await axios.get(
        `http://www.omdbapi.com/?apikey=${process.env.REACT_APP_CLIENT_SECRET}&s=${searchValue}&page=${currentPage}`
    );
    console.log(response.data);
    return res.send(response.data);
});
//FOR PRODUCTION

__dirname = path.resolve();
app.use("/uploads", express.static(path.join(__dirname, "/uploads")));

if (process.env.NODE_ENV === "production") {
    app.use(express.static(path.join(__dirname, "frontend/build")));

    app.get("*", (req, res) =>
        res.sendFile(path.resolve(__dirname, "frontend", "build", "index.html"))
    );
}

const PORT = process.env.PORT || 5000;

app.listen(
    PORT,
    console.log(`server started in ${process.env.NODE_ENV} mode on port ${PORT}`)
);

Users list route. routes/mylist.js

const express = require("express");
const router = express.Router();

const auth = require("../middleware/auth");

const { body, validationResult } = require("express-validator");

const User = require("../models/User");

const Favorite = require("../models/Favorites");

// @route GET api/mylist
// @get a users all lists of movies
// @access Private
router.get("/", auth, async (req, res) => {
    try {
        const favorites = await Favorite.find({ user: req.user.id }).sort({
            date: -1,
        });
        res.json(favorites);
        // console.log(favorites);
    } catch (error) {
        console.error(error.message);
        res.status(500).send("Server Error");
    }
    // res.send("Get the user all lists of movies");
});

// @route POST api/mylist
// @get a users all lists of movies
// @access Private
router.post(
    "/",
    [
        auth,

        [
            body("Poster", "Poster is required").not().isEmpty(),
            body("Title", "imdbid is required").not().isEmpty(),
            body("Type", "Type is required").not().isEmpty(),
            body("Year", "Year is required").not().isEmpty(),
            body("imdbID", "imdbid is required").not().isEmpty(),
        ],
    ],
    async (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        const { Poster, Title, Type, Year, imdbID } = req.body;

        try {
            const newFavorite = new Favorite({
                imdbID,
                Poster,
                Type,
                Year,
                Title,
                user: req.user.id,
            });
            const favorite = await newFavorite.save();
            console.log(newFavorite);
            res.json(favorite);
        } catch (error) {
            console.error(error.message);
            res.status(500).send("Server Error");
        }
        // res.send("Add a movie to mylist ");
    }
);

router.delete(
    "/:id",
    [auth, [body("_id", "_id is required").not().isEmpty()]],
    async (req, res) => {
        try {
            let favorite = await Favorite.findById(req.params.id);

            if (!favorite) return res.status(401).json({ msg: "Movie not found" });

            if (favorite.user.toString() !== req.user.id) {
                return res.status(401).json({ msg: "Not Authorized" });
            }

            await Favorite.findByIdAndRemove(req.params.id);

            res.json({ msg: "Movie Removed" });
            console.log("hey");
        } catch (error) {
            console.error(error.message);
            res.status(500).send("Server Error");
        }
        // res.send("Add a movie to mylist ");
    }
);

module.exports = router;

actions/favorites.js

import axios from "axios";
import {
    GET_FAVORITE_SUCCESS,
    GET_FAVORITE_FAIL,
    ADD_FAVORITE_SUCCESS,
    ADD_FAVORITE_FAIL,
    DELETE_FAVORITE_SUCCESS,
    DELETE_FAVORITE_FAIL,
} from "../actions/types";

import { setAuthToken } from "../utils/setAuthToken";

export const getFavoriteMovies = () => async (dispatch) => {
    console.log("i am here in actions");
    if (localStorage.token) {
        setAuthToken(localStorage.token);
    }
    const config = {
        headers: {
            "Content-Type": "application/json",
        },
    };
    // const body = JSON.stringify({ imdbid });
    try {
        const res = await axios.get("/api/mylist", config);
        console.log(res.data);
        dispatch({
            type: GET_FAVORITE_SUCCESS,
            payload: res.data,
        });
    } catch (error) {
        dispatch({
            type: GET_FAVORITE_FAIL,
            payload: error.response,
        });
    }
};

export const createFavoriteMovies =
    ({ Title, Year, imdbID, Type, Poster }) =>
    async (dispatch) => {
        console.log("i am here in create action");
        console.log({ Title });
        if (localStorage.token) {
            setAuthToken(localStorage.token);
        }
        const config = {
            headers: {
                "Content-Type": "application/json",
            },
        };
        const body = JSON.stringify({ Title, Year, imdbID, Type, Poster });
        try {
            const res = await axios.post("/api/mylist", body, config);
            console.log(res.data);
            dispatch({
                type: ADD_FAVORITE_SUCCESS,
                payload: res.data,
            });
        } catch (error) {
            dispatch({
                type: ADD_FAVORITE_FAIL,
                payload: error.response,
            });
        }
    };

export const deleteFavoriteMovies = (id) => async (dispatch) => {
    console.log("i am here in delete action");
    console.log(id);
    if (localStorage.token) {
        setAuthToken(localStorage.token);
    }
    // const config = {
    //  headers: {
    //      "Content-Type": "application/json",
    //  },
    // };
    try {
        const res = await axios.delete(`/api/mylist/${id}`);
        console.log(res.data);
        dispatch({
            type: DELETE_FAVORITE_SUCCESS,
            payload: res.data,
        });
        console.log(res.data);
    } catch (error) {
        console.log(error);
        dispatch({
            type: DELETE_FAVORITE_FAIL,
            payload: error.response,
        });
    }
};

In this component I want to place that share link through which the user can share it to everyone from which they can see the list with signing up.

components/src/MyList.Js

import React, { useEffect, useState } from "react";
import axios from "axios";
import { useSelector, useDispatch } from "react-redux";
import { getFavoriteMovies } from "../../actions/favorites";
import { deleteFavoriteMovies } from "../../actions/favorites";

import { setAuthToken } from "../../utils/setAuthToken";

import { MyListItems } from "./MyListItems";

import "./MyList.css";

export const MyList = () => {
    // const dispatch = useDisptach();
    const dispatch = useDispatch();
    const favoriteList = useSelector((state) => state.favorites);
    const { favorites } = favoriteList;

    useEffect(() => {
        dispatch(getFavoriteMovies());
    }, [dispatch]);

    console.log(favoriteList);
    return (
        <div className='mylist-container'>
            <h1>My List</h1>
            <button className='add-to-favorite-button'>Share your list</button>
            <div className='movies-container'>
                {" "}
                {favorites !== null &&
                    favorites.map((myList) => (
                        <MyListItems myList={myList} key={myList.id} />
                    ))}
            </div>
        </div>
    );
};

Image of the movie list data fetched for a user as private route.

I want to create a shareable link created by the registered user for this list to show to all other visitors who visits this link


Solution

  • I found the solution of this by creating an API endpoint for the user database that was favorites in my case without wrapping it with auth middleware and fetching the data.

    The code I used for that is in server.js

    
    app.get("/api/share/:id", async (req, res) => {
        const { id } = req.params;
        const favorites = await Favorite.find({ user: id }).sort({
            date: -1,
        });
        res.json(favorites);
    
    });
    
    

    In this way I was able to get the my list data from the database depending upon the user id in the query parameter. I used the relation of the user in the schema of favorites I had.

    const mongoose = require("mongoose");
    
    const FavoritesSchema = mongoose.Schema({
        user: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "user",
        },
        imdbID: {
            type: String,
            require: true,
        },
        Poster: {
            type: String,
            require: true,
        },
        Title: {
            type: String,
            require: true,
        },
        Type: {
            type: String,
            require: true,
        },
        Year: {
            type: String,
            require: true,
        },
        date: {
            type: Date,
            default: Date.now,
        },
    });
    
    module.exports = mongoose.model("favorites", FavoritesSchema);
    

    I was able to solve it like this. Thank you for all the help.