如何在 MERN 应用程序中为私有用户资源创建 public 可共享 link

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

我正在使用 MERN 堆栈创建一个电影 Web 应用程序,用户可以在其中注册和登录。用户还可以将自己喜欢的电影添加到各自的列表中。

现在,我想添加一项功能,用户可以通过可共享的 link 与任何人共享他们创建的列表,就像我们如何在不需要人的情况下授予对 google 驱动器上文件的访问权限一样登录。

请给我一些关于如何实现这一目标的见解。

我要分享的数据库型号。

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}`)
);

用户列出路线。 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,
        });
    }
};

在这个组件中,我想放置那个分享 link,用户可以通过它分享给每个人,他们可以通过注册看到列表。

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>
    );
};

作为私人路线为用户获取的电影列表数据的图像。

我想创建一个由注册用户为此列表创建的共享 link,以显示给访问此 link

的所有其他访问者

您必须将字符串类型数组添加到您的 mongoose scheme.When 用户将电影添加到自己的列表中,这些电影链接将推送您的 array.after,如果您愿意,您可以在任何地方分享它。 添加到您的猫鼬计划 列表:[字符串]

这就是我要做的。

生成一个唯一的 ID,您将使用它来识别收藏夹列表(我将使用 UUID 包 https://www.npmjs.com/package/uuid

假设我生成了一个唯一 ID,我需要将它关联到数据库中的收藏夹列表。

我将使用 node-cache 库 (https://www.npmjs.com/package/node-cache)。

比如我收藏的榜单id是62477be0e5372c02a82b2983

const NodeCache = require( "node-cache" );
const { v4: uuidv4 } = require('uuid');

const favoriteCache = new NodeCache();

const favoriteListId = "62477be0e5372c02a82b2983";
const shareableCode = uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'

favoriteCache.set(shareableCode,favoriteListId);

在上面的代码中,我在缓存中保存了一个唯一的id,并将其值设置为收藏夹列表的id。

现在我想使用代码检索列表。

router.get("/shared/:code", auth, async (req, res) => {
    const {code} = req.params;
    const listId = favoriteCache.get(code);
    if (!listId) return res.status(400).send('invalid share code');
    const list = await Favorite.find({_id: listId});
    res.json(list);
})

如果您重新启动您的应用程序,代码将从缓存中删除,您可以使用数据库来保存代码以使其持久化。但是,如果您希望共享代码持续几个小时,那么这是您可以做到的方法之一。也可以给缓存的set函数设置第三个参数来指定过期时间。

我通过为用户数据库创建一个 API 端点找到了解决方案,在我的例子中是 favorites,没有用 auth 中间件包装它并获取数据.

我使用的代码在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);

});

通过这种方式,我能够根据查询参数中的用户 ID 从数据库中获取我的列表数据。 我在收藏夹模式中使用了用户关系。

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);

我是这样解决的。 感谢大家的帮助。