用户发帖功能

前端实现

前端主要通过与后端 API 交互来实现用户发帖。在前端组件中,可能有一个创建帖子的表单组件,用户输入文本和图片后,调用 fetch 方法向后端发送请求。从现有的代码中,虽然没有看到完整的创建帖子组件,但可以推测可能存在类似 CreatePost.jsx 的组件。

后端实现

后端在 <mcfile name="postController.js" path="/Applications/Work/CodingProject/threads-clone/backend/controllers/postController.js"></mcfile> 文件中实现了 createPost 函数:

// ... existing code ...
const createPost = async (req, res) => {
    try {
        const { postedBy, text } = req.body;
        let { img } = req.body;

        if (!postedBy || !text) {
            return res.status(400).json({ error: "Postedby and text fields are required" });
        }

        const user = await User.findById(postedBy);
        if (!user) {
            return res.status(404).json({ error: "User not found" });
        }

        if (user._id.toString() !== req.user._id.toString()) {
            return res.status(401).json({ error: "Unauthorized to create post" });
        }

        const maxLength = 500;
        if (text.length > maxLength) {
            return res.status(400).json({ error: `Text must be less than ${maxLength} characters` });
        }

        if (img) {
            const uploadedResponse = await cloudinary.uploader.upload(img);
            img = uploadedResponse.secure_url;
        }

        const newPost = new Post({ postedBy, text, img });
        await newPost.save();

        res.status(201).json(newPost);
    } catch (err) {
        res.status(500).json({ error: err.message });
        console.log(err);
    }
};
// ... existing code ...

此函数首先验证用户输入,然后检查用户权限,接着处理图片上传,最后将帖子保存到数据库。

用户评论功能

前端实现

前端在 <mcfile name="Comment.jsx" path="/Applications/Work/CodingProject/threads-clone/frontend/src/components/Comment.jsx"></mcfile> 组件中展示评论,同时可能有一个输入框组件让用户输入评论内容,并调用后端 API 提交评论。

// ... existing code ...
const Comment = ({ reply, lastReply }) => {
    return (
        <>
            <Flex gap={4} py={2} my={2} w={"full"}>
                <Avatar src={reply.userProfilePic} size={"sm"} />
                <Flex gap={1} w={"full"} flexDirection={"column"}>
                    <Flex w={"full"} justifyContent={"space-between"} alignItems={"center"}>
                        <Text fontSize='sm' fontWeight='bold'>
                            {reply.username}
                        </Text>
                    </Flex>
                    <Text>{reply.text}</Text>
                </Flex>
            </Flex>
            {!lastReply ? <Divider /> : null}
        </>
    );
};
// ... existing code ...

后端实现

后端在 <mcfile name="postController.js" path="/Applications/Work/CodingProject/threads-clone/backend/controllers/postController.js"></mcfile> 文件中实现了 replyToPost 函数:

// ... existing code ...
const replyToPost = async (req, res) => {
    try {
        const { text } = req.body;
        const postId = req.params.id;
        const userId = req.user._id;
        const userProfilePic = req.user.profilePic;
        const username = req.user.username;

        if (!text) {
            return res.status(400).json({ error: "Text field is required" });
        }

        const post = await Post.findById(postId);
        if (!post) {
            return res.status(404).json({ error: "Post not found" });
        }

        const reply = { userId, text, userProfilePic, username };

        post.replies.push(reply);
        await post.save();

        res.status(200).json(reply);
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};
// ... existing code ...

该函数接收用户输入的评论内容,找到对应的帖子,将评论添加到帖子的 replies 数组中。

在线聊天功能

前端实现

前端在 <mcfile name="Conversation.jsx" path="/Applications/Work/CodingProject/threads-clone/frontend/src/components/Conversation.jsx"></mcfile> 组件中展示聊天会话,同时可能使用 socket.io-client 库与后端建立实时连接。

// ... existing code ...
const Conversation = ({ conversation, isOnline }) => {
    const user = conversation.participants[0];
    const currentUser = useRecoilValue(userAtom);
    const lastMessage = conversation.lastMessage;
    const [selectedConversation, setSelectedConversation] = useRecoilState(selectedConversationAtom);
    const colorMode = useColorMode();

    return (
        <Flex
            gap={4}
            alignItems={"center"}
            p={"1"}
            _hover={{
                cursor: "pointer",
                bg: useColorModeValue("gray.600", "gray.dark"),
                color: "white",
            }}
            onClick={() =>
                setSelectedConversation({
                    _id: conversation._id,
                    userId: user._id,
                    userProfilePic: user.profilePic,
                    username: user.username,
                    mock: conversation.mock,
                })
            }
            bg={
                selectedConversation?._id === conversation._id ? (colorMode === "light" ? "gray.400" : "gray.dark") : ""
            }
            borderRadius={"md"}
        >
            <WrapItem>
                <Avatar
                    size={{
                        base: "xs",
                        sm: "sm",
                        md: "md",
                    }}
                    src={user.profilePic}
                >
                    {isOnline ? <AvatarBadge boxSize='1em' bg='green.500' /> : ""}
                </Avatar>
            </WrapItem>

            <Stack direction={"column"} fontSize={"sm"}>
                <Text fontWeight='700' display={"flex"} alignItems={"center"}>
                    {user.username} <Image src='/verified.png' w={4} h={4} ml={1} />
                </Text>
                <Text fontSize={"xs"} display={"flex"} alignItems={"center"} gap={1}>
                    {currentUser._id === lastMessage.sender ? (
                        <Box color={lastMessage.seen ? "blue.400" : ""}>
                            <BsCheck2All size={16} />
                        </Box>
                    ) : (
                        ""
                    )}
                    {lastMessage.text.length > 18
                        ? lastMessage.text.substring(0, 18) + "..."
                        : lastMessage.text || <BsFillImageFill size={16} />}
                </Text>
            </Stack>
        </Flex>
    );
};
// ... existing code ...