-
카테고리
-
세부 분야
풀스택
-
해결 여부
미해결
안녕하세요 제로초님 이미지 업로드 관련 질문이 있습니다.
23.11.04 22:28 작성 조회수 269
0
이미지 업로드를 위한 multer 강의까지 수강을 하였습니다.
이미지 업로드를 할때 uploads 폴더에도 이미지가 잘 들어가고
UPLOAD_IMAGES_SUCCESS도 잘 나오는 상황입니다.
하지만 제로초님의 화면은 제거 버튼과 비록 깨지지만 이미지가 올라간 화면이 보이는데 저는 그 부분이 나오지 않아서 이 점이 궁금하여 질문 드립니다.
import React, { useState } from "react";
import { Button, Card, Popover, Avatar, Image, List, Comment } from "antd";
import {
RetweetOutlined,
HeartOutlined,
MessageOutlined,
EllipsisOutlined,
HeartTwoTone,
} from "@ant-design/icons";
import { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import PropTypes from "prop-types";
import PostImages from "./PostImages";
import CommentForm from "./CommentForm";
import PostCardContent from "./PostCardContent";
import {
REMOVE_POST_REQUEST,
LIKE_POST_REQUEST,
UNLIKE_POST_REQUEST,
} from "../reducers/post";
import FollowButton from "./FollowButton";
const PostCard = ({ post }) => {
//pages/index.js에서 mainPosts에서 하나씩 뜯어서 보내줌
const dispatch = useDispatch();
const [commentFormOpened, setCommentFormOpened] = useState(false);
//댓글창 열지 말지
const onLike = useCallback(() => {
dispatch({
type: LIKE_POST_REQUEST,
data: post.id,
});
}, []); //좋아요
const onUnlike = useCallback(() => {
dispatch({
type: UNLIKE_POST_REQUEST,
data: post.id,
});
}, []); //좋아요 취소
const onToggleComment = useCallback(() => {
setCommentFormOpened((prev) => !prev);
}, []); //폼 버튼 한번 더 누르면 폼 닫기
const onRemovePost = useCallback(() => {
return dispatch({
type: REMOVE_POST_REQUEST,
data: post.id,
});
}, [id]);
const id = useSelector((state) => state.user.me?.id);
const { removePostloading } = useSelector((state) => state.post);
const liked = post.Likers.find((v) => v.id === id); //게시글 좋아요 누른 사람 중에 내가 있는지.
return (
<div style={{ marginBottom: 20 }}>
<Card
cover={post.Images?.[0] && <PostImages images={post.Images} />}
//이미지가 존재한다면 PostImages를 출력
actions={[
//카드 아래에 존재하는 것들
<RetweetOutlined key="retweet" />,
liked ? (
<HeartTwoTone twoToneColor="red" onClick={onUnlike} />
) : (
<HeartOutlined key="heart" onClick={onLike} />
),
<MessageOutlined onClick={onToggleComment} key="comment" />,
<Popover //더보기 같은 역할
key="more"
content={
<Button.Group>
{id && post.User.id === id ? (
<>
{/* 내가 쓴 글이면 수정, 삭제 */}
<Button>수정</Button>
<Button
type="danger"
onClick={onRemovePost}
loading={removePostloading}
>
삭제
</Button>
</>
) : (
// 내가 쓴 글이 아니라면
<Button>신고</Button>
)}
</Button.Group>
}
>
<EllipsisOutlined />
</Popover>,
]}
extra={id && <FollowButton post={post} />}
>
<Card.Meta //프로필과 내용 등
avatar={<Avatar>{post.User.nickname[0]}</Avatar>}
title={post.User.nickname}
description={<PostCardContent postData={post.content} />}
/>
</Card>
{commentFormOpened && (
//commentFormOpened가 true이면 열어라
<div>
{/* 어떤 게시글에 댓글을 남기는지.. */}
<CommentForm post={post} />
<List
header={`${post.Comments.length}개의 댓글`}
itemLayout="horizontal"
dataSource={post.Comments} //데이터는 여기서 가져와서
renderItem={(
item //이런식으로 출력한다
) => (
<li>
<Comment
author={item.User.nickname} //댓글쓴사람
avatar={
<Avatar>{item.User.nickname[0]}</Avatar> //아바타
}
content={item.content}
/>
</li>
)}
/>
</div>
)}
</div>
);
};
PostCard.PropTypes = {
post: PropTypes.shape({
id: PropTypes.number,
User: PropTypes.object,
content: PropTypes.string,
createdAt: PropTypes.string,
Comment: PropTypes.arrayOf(PropTypes.object),
Images: PropTypes.arrayOf(PropTypes.object),
Likers: PropTypes.arrayOf(PropTypes.object),
}).isRequired,
};
export default PostCard;
저의 PostCard 코드입니다 이 코드에서 cover={post.Images?.[0] && <PostImages images={post.Images} />}
이 부분이 이미지들을 출력해주는 부분이 아닌가요?? 저의 화면에는 아래처럼 나오지 않습니다.
답변을 작성해보세요.
0
조현영
지식공유자2023.11.05
imagePaths는 아무 관련이 없습니다. 현재 post는 mainPosts의 값을 반복문을 돌아 렌더링하는 겁니다. Images 안에가 빈 배열이기 때문에 이미지가 없는 겁니다. Images 안에를 채우세요.
서버에서 데이터 불러올 때 Images 까지 include 해서 쓰시면 됩니다.
윤채현
질문자2023.11.05
1) routes/post.js
const express = require("express");
const multer = require("multer");
const path = require("path");
const fs = require("fs");
const { Post, Image, Comment, User, Hashtag } = require("../models");
const { isLoggedIn } = require("./middlewares");
const router = express.Router();
try {
fs.accessSync("uploads");
} catch (error) {
console.log("uploads폴더가 없으므로 생성합니다.");
fs.mkdirSync("uploads");
}
router.post("/", isLoggedIn, async (req, res, next) => {
// 보기에는 "/"로 되어있지만 실제로는 "/post"로 되어있다.
try {
const post = await Post.create({
content: req.body.content,
UserId: req.user.id,
});
const fullPost = await Post.findOne({
where: { id: post.id },
include: [
{
model: Image,
},
{
model: Comment,
include: [
{
model: User, //댓글 작성자
attributes: ["id", "nickname"],
},
],
},
{
model: User, //게시글 작성자
attributes: ["id", "nickname"],
},
{
model: User, //좋아요 누른 사람
as: "Likers",
attributes: ["id"],
},
],
});
res.status(201).json(fullPost); //다시 프론트로 돌려주기
} catch (error) {
console.error(error);
next(error);
}
});
router.post("/:postId/comment", isLoggedIn, async (req, res, next) => {
//:postId는 동적으로 바뀐다.
//POST /post/comment
// 보기에는 "/"로 되어있지만 실제로는 "/post"로 되어있다.
try {
const post = await Post.findOne({
//이 게시물이 진짜 있는지.
where: { id: req.params.postId },
});
if (!post) {
return res.status(403).send("존재하지 않는 게시글입니다.");
}
const comment = await Comment.create({
content: req.body.content,
PostId: parseInt(req.params.postId, 10), //문자열로 넘어가기 때문에 int형으로 바꿔줘야한다.
UserId: req.user.id,
});
const fullComment = await Comment.findOne({
where: { id: comment.id },
include: [
{
model: User,
attributes: ["id", "nickname"],
},
],
});
res.status(201).json(fullComment);
} catch (error) {
console.error(error);
next(error);
}
});
router.patch("/:postId/like", isLoggedIn, async (req, res, next) => {
//PATCH /post/1/like\
try {
const post = await Post.findOne({ where: { id: req.params.postId } });
if (!post) {
return res.status(403).send("게시글이 존재하지 않습니다.");
}
await post.addLikers(req.user.id);
res.json({ PostId: post.id, UserId: req.user.id });
} catch (error) {
console.error(error);
next(error);
}
});
router.delete("/:postId/like", isLoggedIn, async (req, res, next) => {
//DELETE /post/1/like
try {
const post = await Post.findOne({ where: { id: req.params.postId } });
if (!post) {
return res.status(403).send("게시글이 존재하지 않습니다.");
}
await post.removeLikers(req.user.id);
res.json({ PostId: post.id, UserId: req.user.id });
} catch (error) {
console.error(error);
next(error);
}
});
router.delete("/:postId", isLoggedIn, async (req, res, next) => {
// DELETE /post/10
try {
await Post.destroy({
where: {
id: req.params.postId,
UserId: req.user.id,
},
});
res.status(200).json({ PostId: parseInt(req.params.postId, 10) });
} catch (error) {
console.error(error);
next(error);
}
});
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, "uploads");
},
filename(req, file, done) {
// 제로초.png
const ext = path.extname(file.originalname); // 확장자 추출(.png)
const basename = path.basename(file.originalname, ext); // 제로초
done(null, basename + "_" + new Date().getTime() + ext); // 제로초15184712891.png
},
}),
limits: { fileSize: 20 * 1024 * 1024 }, // 20MB
});
router.post(
"/images",
isLoggedIn,
upload.array("image"), //PostForm.js에서input에 올린 이미지가 배열로 들어감 (이미지를 여러장 올릴 수 있게 하기 위해서)
(req, res, next) => {
// POST /post/images/
//이곳은 이미지 업로드 후 실행되는 부분, 업로드는 위에 upload에서 이미 다 올라감
console.log(req.files);
res.json(req.files.map((v) => v.filename)); //프론트로 보내줌
}
);
module.exports = router;
//node에서는 import와 export defau lt를 사용하지 않고 require를 사용한다.
2) back/app.js
const express = require("express");
const postRouter = require("./routes/post");
const postsRouter = require("./routes/posts");
const userRouter = require("./routes/user");
const cors = require("cors");
const session = require("express-session");
const morgan = require("morgan");
const passport = require("passport");
const cookieParser = require("cookie-parser");
const dotenv = require("dotenv");
const path = require("path");
const app = express(); //익스프레스 서버
const db = require("./models");
const passportConfig = require("./passport");
db.sequelize
.sync()
.then(() => {
console.log("db 연결 성공");
})
.catch(console.error);
dotenv.config();
passportConfig();
app.use(morgan("dev"));
app.use(
cors({
origin: "http://localhost:3060",
credentials: true, //도메인이 달라도 보낼수 있어짐 3060 -> 3065로 쿠키를 보내준다.
})
); //cors설정
//res.setHeader("Access-Control-Allow-Origin", "http://localhost:3060");
//CORS해결법 *은 모든 주소에 대해서 라는 뜻
//localhost 3060에서 온 것은 허용해주게싸
app.use(express.json());
//익스프레스서버에 뭔가를 장착하겠다.
//프론트에서 Json 형식으로 받은 것을 req.body에 넣어준다.
app.use(express.urlencoded({ extended: true }));
//form에서 제출한 것을 넘겨준다.
//front에서 보낸 action.data를 req.body에 넣어주느 역할
app.use("/", express.static(path.join(__dirname, "uploads")));
app.use(
session({
saveUninitialized: false,
resave: false,
secret: process.env.COOKIE_SECRET,
}) //실제적인 정보를 프론트로 넘기면 안되기때문에, 랜덤한 문자열을 넘겨서 백서버와 프론트서버에 동일한 정보를 가질 수 있게 만들어줌
);
app.use(passport.initialize());
app.use(passport.session());
app.use(cookieParser(process.env.COOKIE_SECRET));
app.get("/", (req, res) => {
res.send("hello express");
});
app.get("/api", (req, res) => {
res.send("hello api");
});
app.use("/posts", postsRouter); //"/post"가 중복되므로 앞으로 뽑아줄 수 있다.
app.use("/post", postRouter); //"/post"가 중복되므로 앞으로 뽑아줄 수 있다.
app.use("/user", userRouter); //"/post"가 중복되므로 앞으로 뽑아줄 수 있다.
app.listen(3065, () => {
console.log("서버 실행 중");
});
이게 제 코드입니다..
수정해보려고 노력중인데 도저히 어느 부분이 틀린지 모르겠네요ㅠ
조현영
지식공유자2023.11.05
지금 게시글 등록할 때 이미지를 같이 등록하지 않고 계십니다.
https://github.com/ZeroCho/react-nodebird/blob/master/ch5/back/routes/post.js#L44
강의 코드가 많이 누락되었는데 다 따라하신 것 맞나요?
답변 1