Commit 35f41d6b authored by Lee Soobeom's avatar Lee Soobeom
Browse files

Merge remote-tracking branch 'origin/sb15' into develop

parents 4fec5250 7285bfbd
......@@ -29,7 +29,6 @@ export const App = () => {
<Route path="board" element={<Board />} />
<Route path="post/:postId" element={<IntoPost />} />
<Route path="edit" element={<EditPost />} />
{/* </Route> */}
<Route
path="profile"
element={
......@@ -39,7 +38,8 @@ export const App = () => {
}
/>
<Route path="admin" element={<Admin />} />
<Route path="admin/:imgId" element={<ImgRewrite/>}/>
<Route path="admin/:imgId" element={<ImgRewrite />} />
<Route path="rewrite" element={<ImgRewrite />} />
</Route>
</Route>
</Routes>
......
......@@ -2,39 +2,39 @@ import axios from "axios";
import baseUrl from "./baseUrl";
import { PostType } from "../types";
export const posting = async (post: PostType) => {
const { data } = await axios.post(`${baseUrl}/posts/`, post);
export const createFileAndPost = async (formdata: FormData) => {
const { data } = await axios.post(`${baseUrl}/posts/`, formdata);
return data;
};
export const getData = async () => {
const { data } = await axios.get(`${baseUrl}/posts/`);
return data;
};
}; //board
export const addCounts = async (_id: string, counts: number) => {
const { data } = await axios.post(`${baseUrl}/posts/${_id}`, {
counts: counts + 1,
});
export const getFileByPostId = async (postId: string) => {
const { data } = await axios.get(`${baseUrl}/posts/files/${postId}`);
return data;
};
}; //
export const getPostByPostId = async (_id: string) => {
const { data } = await axios.get(`${baseUrl}/posts/${_id}`);
export const getImgData = async (name: string) => {
const { data } = await axios.get(`/images/${name}`);
return data;
};
export const deletePost = async (_id: string) => {
const { data } = await axios.delete(`${baseUrl}/posts/${_id}`);
export const addCounts = async (postId: string, counts: number) => {
const { data } = await axios.post(`${baseUrl}/posts/${postId}`, {
counts: counts + 1,
});
return data;
};
export const updating = async (post: PostType) => {
const { data } = await axios.put(`${baseUrl}/posts/${post._id}`, post);
export const deletePost = async (postId: string) => {
const { data } = await axios.delete(`${baseUrl}/posts/${postId}`);
return data;
};
export const postImg = async (formdata: FormData) => {
const { data } = await axios.post(`${baseUrl}/posts`, formdata);
export const updateImgAndPost = async (postId: string, formdata: FormData) => {
const { data } = await axios.put(`${baseUrl}/posts/${postId}`, formdata);
return data;
};
......@@ -13,8 +13,8 @@ export default function BoardPage() {
useEffect(() => {
getDataList();
}, [posts]);
}, []);
// posts
const getDataList = async () => {
const res = await postApi.getData();
setPosts(res);
......@@ -37,22 +37,22 @@ export default function BoardPage() {
return (
<div className="flex flex-col items-center">
<div className="flex flex-col items-center mt-6">
<div className="flex flex-col w-10/12 items-center mt-6">
<div>`Travel Report's Board`</div>
<div>`여행지 후기를 남겨주세요!`</div>
</div>
<div className="flex flex-col w-10/12 mt-16">
<div className="flex justify-end">
<div className="border-2 mb-2">
<div className="border-2 border-blue-500 rounded mb-2">
<Link to="/posting">
<button>글쓰기+</button>
<button>글쓰기</button>
</Link>
</div>{" "}
{/* Link */}
</div>
<div className="sm:overflow-y-scroll">
<div className="flex flex-row divide-x-2 border-2 border-solid bg-sky-300 border-y-2 h-10 ">
<div className="flex flex-row divide-x-2 border-2 border-solid border-y-2 h-10 bg-gradient-to-r from-cyan-500 to-blue-500 ">
<div className="basis-full">Title</div>
<div className="basis-3/12">Date</div>
<div className="basis-2/12">Clicks</div>
......
import React, { FormEvent, useState } from "react";
import React, { FormEvent, useState, useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import isLength from "validator/lib/isLength";
import equals from "validator/lib/equals";
......@@ -6,12 +6,16 @@ import { catchErrors } from "../helpers";
import { PostType } from "../types";
import { postApi } from "../apis";
import { PostState } from "./intopost";
import { FileType } from "./intopost";
export function EditPost() {
const [city, setCity] = useState<string>("질문종류");
const [theme, setTheme] = useState<string>("질문종류");
const [city, setCity] = useState<string>("city");
const [theme, setTheme] = useState<string>("theme");
const [title, setTitle] = useState<string>("");
const [text, setText] = useState<string>("");
const [file, setFile] = useState<FileList>();
const [imgSrc, setImgSrc] = useState<string[]>();
const [filesList, setFilesList] = useState<FileType[]>();
const navigate = useNavigate();
const location = useLocation() as PostState;
......@@ -24,7 +28,7 @@ export function EditPost() {
city: post.city,
date: post.date,
user: post.user,
counts: 0,
counts: post.counts,
_id: post._id,
});
......@@ -33,21 +37,51 @@ export function EditPost() {
const [disabled, setDisabled] = useState(false);
const [success, setSuccess] = useState(false);
async function reWriteSubmit(event: FormEvent) {
event.preventDefault();
try {
setError("");
useEffect(() => {
getFilesList(post._id);
}, []);
console.log("user data", user);
const imgArr = new Array();
if (postingFormMatch(user) === true) {
setLoading(true);
const res = await postApi.updating(user);
const getFilesList = async (postId: string) => {
const res = await postApi.getFileByPostId(postId); //_id는 req.params에 항상 같이 보낸다
setFilesList(res);
};
const updateImg2Db = async (filelist: FileList) => {
const formdata = new FormData();
formdata.append("title", user.title);
formdata.append("text", user.text);
formdata.append("theme", user.theme);
formdata.append("city", user.city);
if (filelist === undefined || filelist === null) {
const res = await postApi.updateImgAndPost(user._id, formdata);
} else {
for (var i = 0; i < filelist.length; i++) {
formdata.append("picture", filelist?.[i]);
}
const res = await postApi.updateImgAndPost(user._id, formdata);
}
};
console.log("clear", res);
navigate("/board", { replace: true });
setSuccess(true);
async function reWriteSubmit(event: FormEvent) {
event.preventDefault();
try {
if (confirm("게시물을 수정하시겠습니까?") == true) {
setError("");
// console.log("user data", user);
if (postingFormMatch(user) === true) {
setLoading(true);
if (file) {
const res = updateImg2Db(file);
// console.log(res);
}
navigate("/board", { replace: true });
setSuccess(true);
setError("");
}
} else {
return false;
}
} catch (error) {
console.log("에러발생");
......@@ -57,19 +91,48 @@ export function EditPost() {
}
}
const handleInputPic = async (event: React.ChangeEvent<HTMLInputElement>) => {
const maxImg = 10;
const { files } = event.target;
if (!(files === null)) {
setFile(files);
}
if (!(files?.length === undefined)) {
if (files?.length <= maxImg) {
for (var i = 0; i < files.length; i++) {
const reader = new FileReader();
reader.readAsDataURL(files?.[i]);
reader.onload = (e) => {
imgArr.push(e.target?.result);
setImgSrc(imgArr);
};
}
} else {
alert(`사진은 최대 ${maxImg}장까지 업로드 가능합니다!`);
}
}
};
function postingFormMatch(user: PostType) {
if (!isLength(user.title ?? "", { min: 1 })) {
setError("제목을 입력해 주세요.");
alert("제목을 입력해 주세요.");
return false;
} else if (!isLength(user.text ?? "", { min: 1 })) {
alert("내용을 입력해 주세요.");
setError("내용을 입력해 주세요.");
return false;
} else if (equals(city, "city")) {
setError("테마를 선택해 주세요.");
return false;
} else if (equals(theme, "theme")) {
} else if (equals(user.city, "city")) {
alert("도시를 선택해 주세요.");
setError("도시를 선택해 주세요.");
return false;
} else if (equals(user.theme, "theme")) {
alert("테마를 선택해 주세요.");
setError("테마를 선택해 주세요.");
return false;
} else {
return true;
}
......@@ -108,75 +171,104 @@ export function EditPost() {
};
return (
<div className="flex flex-col border-3">
<form onSubmit={reWriteSubmit} className="w-full items-center">
<div className="flex flex-row relative">
<p className="basis-1/12 gap-x-8">Id</p>
<p className="basis-8/12 invisible">empty</p>
<select
name="city"
className="border border-3 border-black w-1/12"
onChange={cityChange}
defaultValue={post.city}
>
<option value="city">도시</option>
<option value="Seoul">서울</option>
<option value="Busan">부산</option>
<option value="Incheon">인천</option>
<option value="Daegoo">대구</option>
<option value="Kwangjoo">광주</option>
<option value="Daejeon">대전</option>
<option value="Woolsan">울산</option>
<option value="Sejong">세종</option>
<option value="Dokdo">독도</option>
<option value="Jeju">제주</option>
</select>
<select
name="theme"
className="border border-3 border-black w-1/12"
onChange={themeChange}
defaultValue={post.theme}
>
<option value="theme">테마</option>
<option value="cycling">사이클링</option>
<option value="surfing">서핑</option>
<option value="activity">액티비티</option>
<option value="camping">캠핑</option>
<option value="sking">스키</option>
<option value="boat">보트</option>
<option value="desert">사막</option>
<option value="golf">골프</option>
<option value="cave">동굴</option>
<option value="history">문화재</option>
<option value="zoo">동물원</option>
<option value="cycling">사이클링</option>
</select>
<button
type="submit"
className="border border-black basis-1/12 gap-x-8"
>
Rewrite
</button>
<div className="flex place-content-center">
<form
onSubmit={reWriteSubmit}
className="flex flex-col w-96 items-center"
>
<div className="flex flex-row h-8 gap-x-1">
<div className="flex border-2 border-sky-400 rounded-full w-20 place-content-center transition ease-in-out delay-150 bg-white-400 hover:-translate-y-1 hover:scale-110 hover:bg-gray-300 duration-300">
<input
id="files"
type="file"
multiple
onChange={handleInputPic}
className="hidden"
/>
<label htmlFor="files" className="text-xs mt-1.5 ml-1 ">
파일 선택
</label>
</div>
<div className="flex w-20">
<select
name="city"
className="border-2 border-sky-400 rounded-full w-20 text-xs"
onChange={cityChange}
defaultValue={post.city}
>
<option value="city">도시</option>
<option value="Seoul">서울</option>
<option value="Busan">부산</option>
<option value="Incheon">인천</option>
<option value="Daegu">대구</option>
<option value="Gwangju">광주</option>
<option value="Daejeon">대전</option>
<option value="Woolsan">울산</option>
<option value="Sejong">세종</option>
<option value="Dokdo">독도</option>
<option value="Jeju">제주</option>
</select>
</div>
<div className="flex w-20">
<select
name="theme"
className="border-2 border-sky-400 rounded-full w-20 text-xs"
onChange={themeChange}
defaultValue={post.theme}
>
<option value="theme">테마</option>
<option value="cycling">사이클링</option>
<option value="surfing">서핑</option>
<option value="activity">액티비티</option>
<option value="camping">캠핑</option>
<option value="sking">스키</option>
<option value="boat">보트</option>
<option value="desert">사막</option>
<option value="golf">골프</option>
<option value="cave">동굴</option>
<option value="history">문화재</option>
<option value="zoo">동물원</option>
<option value="cycling">사이클링</option>
</select>
</div>
<div className="flex w-20">
<button
type="submit"
className="border-2 border-sky-400 rounded-full w-20 text-xs text-center transition delay-150 bg-white-400 hover:-translate-y-1 hover:scale-110 hover:bg-sky-300 duration-300"
>
수정
</button>
</div>
</div>
<div className="flex border-4">
<div className="flex flex-col">
<textarea
name="title"
onChange={titleChange}
placeholder="title"
className="w-full h-8"
defaultValue={post.title}
></textarea>
</div>
<div className="flex border-2">
placeholder="제목을 입력해 주세요!"
className="flex w-96 border-2 border-sky-500 rounded"
/>
<div className="flex flex-col mt-1 mb-1">
<div className="flex gap-x-2 h-48 overflow-x-scroll">
{filesList?.map((file, i) => (
<img
key={i}
src={"http://localhost:3000/images/" + file.newfilename}
width={200}
height={200}
/>
))}
</div>
</div>
<textarea
onChange={textChange}
name="text"
placeholder="text"
className="w-full h-96"
defaultValue={post.text}
></textarea>
placeholder="여행 후기를 알려주세요!"
className="flex w-96 h-96 border-2 border-sky-500 rounded"
/>
</div>
</form>
</div>
......
import React, { MouseEvent } from "react";
import React, { MouseEvent, useEffect, useState } from "react";
import { useLocation, useNavigate, Link, Outlet } from "react-router-dom";
import { catchErrors } from "../helpers";
import { postApi } from "../apis";
import { PostType } from "../types";
......@@ -7,59 +8,118 @@ export interface PostState {
state: PostType;
}
export interface FileType {
id: string;
post: string;
originalfilename: string;
newfilename: string;
picturepath: string;
}
export interface FilesList {
filesList: FileType[];
}
export function IntoPost() {
const location = useLocation() as PostState;
const post = location.state;
const navigate = useNavigate();
const [filesList, setFilesList] = useState<FileType[]>();
// console.log(post);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [addSuccess, setAddSuccess] = useState(false);
const [delSuccess, setDelSuccess] = useState(false);
useEffect(() => {
getFilesList(post._id);
console.log("newfilename", filesList?.[0].newfilename);
}, []);
const getFilesList = async (postId: string) => {
const res = await postApi.getFileByPostId(postId);
setFilesList(res);
};
const handleDeleteClick = async (event: MouseEvent<HTMLButtonElement>) => {
const postId = event.currentTarget.id;
const res = await postApi.deletePost(postId);
navigate("/board", { replace: true });
console.log("delete post", res);
try {
if (confirm("삭제하시겠습니까?") == true) {
const postId = event.currentTarget.id;
const res = await postApi.deletePost(postId);
navigate("/board", { replace: true });
console.log("delete post", res);
} else {
return false;
}
} catch (error) {
console.log("에러발생");
catchErrors(error, setError);
}
};
return (
<div>
<div>
<div>
<div className="flex flex-row basis-8 gap-x-1">
<div className="border-2 border-sky-300 border-current rounded">
<button id={post._id} onClick={handleDeleteClick}>
<div className="flex flex-row h-8 gap-x-1 place-content-end">
<div className="w-8">
<button
id={post._id}
onClick={handleDeleteClick}
className="border-2 border-sky-100 rounded-full h-8 w-8 text-xs text-center transition delay-150 bg-white-400 hover:-translate-y-1 hover:scale-110 hover:bg-red-300 duration-300"
>
삭제
</button>
</div>
<div className="border-2 border-sky-300 border-current rounded">
<div className="w-8">
<Link to="/edit" state={post}>
<button>수정</button>
<button className="border-2 border-sky-100 rounded-full h-8 w-8 text-xs transition delay-150 bg-white-400 hover:-translate-y-1 hover:scale-110 hover:bg-sky-300 duration-300">
수정
</button>
</Link>
</div>
</div>
<div className="flex flex-row gap-x-1">
<div className="flex basis-3/4 border-2 border-sky-300 rounded">
제목: {post.title}
<div className="flex flex-col h-16 md:h-8 md:flex-row">
<div className="flex basis-1/2 place-content-between h-8">
<div className="flex basis-1/2 border-2 border-sky-200 rounded h-8">
작성자: {post.user.slice(0, 8)}
</div>
<div className="flex basis-1/2 border-2 border-sky-200 rounded h-8">
작성일: {post.date.slice(0, 10)}
</div>
</div>
<div className="flex basis-1/4 border-2 border-sky-300 rounded">
작성자: {post.user}
<div className="flex basis-1/2 place-content-between h-8">
<div className="flex basis-1/3 border-2 border-sky-300 rounded">
도시: {post.city}
</div>
<div className="flex basis-1/3 border-2 border-sky-300 rounded">
테마: {post.theme}
</div>
<div className="flex basis-1/3 border-2 border-sky-300 rounded">
조회수: {post.counts}
</div>
</div>
</div>
<div className="flex flex-row gap-x-1">
<div className="flex basis-1/4 border-2 border-sky-300 rounded">
도시: {post.city}
</div>
<div className="flex basis-1/4 border-2 border-sky-300 rounded">
테마: {post.theme}
</div>
<div className="flex basis-1/4 border-2 border-sky-300 rounded">
작성일: {post.date}
</div>
<div className="flex basis-1/4 border-2 border-sky-300 rounded">
조회수: {post.counts}
<div className="flex flex-row h-8 gap-x-1 ">
<div className="flex w-full border-2 border-sky-200 rounded">
제목: {post.title}
</div>
</div>
</div>
<div className="border-2 border-sky-300 rounded h-96">{post.text}</div>
<div className="flex-row border-2 border-sky-400 rounded h-48 gap-x-2 ">
<div className="flex gap-x-2 h-48 overflow-x-scroll">
{filesList?.map((file, i) => (
<img
key={i}
src={"http://localhost:3000/images/" + file.newfilename}
width={200}
height={200}
/>
))}
</div>
</div>
<div className="border-2 border-sky-500 rounded h-96">{post.text}</div>
</div>
</div>
);
......
......@@ -18,7 +18,7 @@ export default function Post({ handleClick, post }: Props) {
</button>
</Link>
</div>
<div className="basis-3/12">{post.date}</div>
<div className="basis-3/12">{post.date.slice(0, 10)}</div>
<div className="basis-2/12">{post.counts}</div>
</div>
);
......
......@@ -5,14 +5,13 @@ import equals from "validator/lib/equals";
import { catchErrors } from "../helpers";
import { PostType } from "../types";
import { postApi } from "../apis";
import { File } from "formidable";
export default function Posting() {
const [city, setCity] = useState<string>("city");
const [theme, setTheme] = useState<string>("theme");
const [title, setTitle] = useState<string>("");
const [text, setText] = useState<string>("");
const [file, setFile] = useState<File>();
const [file, setFile] = useState<FileList>();
const [imgSrc, setImgSrc] = useState<string[]>();
const navigate = useNavigate();
......@@ -32,38 +31,48 @@ export default function Posting() {
const [disabled, setDisabled] = useState(false);
const [success, setSuccess] = useState(false);
useEffect(() => {
console.log("uploaded imgs", imgSrc);
}, [imgSrc]);
const imgArr = new Array();
const sendImg2Db = async (filelist: FileList) => {
const formdata = new FormData();
formdata.append("title", user.title);
formdata.append("text", user.text);
formdata.append("theme", user.theme);
formdata.append("city", user.city);
if (!(filelist === undefined || filelist === null)) {
for (var i = 0; i < filelist.length; i++) {
formdata.append(`picture${i}`, filelist?.[i]);
if (filelist.length === 1) {
formdata.append("picture", filelist?.[0]);
const res = await postApi.createFileAndPost(formdata);
} else {
for (var i = 0; i < filelist.length; i++) {
formdata.append("picture", filelist?.[i]);
}
const res = await postApi.createFileAndPost(formdata);
}
console.log("formdata", formdata);
await postApi.postImg(formdata);
}
};
async function handlePostSubmit(event: FormEvent) {
event.preventDefault();
try {
setError("");
console.log("user data", user);
if (postingFormMatch()) {
setLoading(true);
// sendImg2Db();
const res = await postApi.posting(user);
console.log("서버연결됬나요", res);
// console.log("user save");
navigate("/board", { replace: true });
setSuccess(true);
if (confirm("게시물을 작성하시겠습니까?") == true) {
setError("");
// console.log("user data", user);
if (!(imgSrc === undefined)) {
if (postingFormMatch(user, imgSrc)) {
setLoading(true);
if (file) {
const res = sendImg2Db(file);
// console.log(res);
}
}
navigate("/board", { replace: true });
setSuccess(true);
setError("");
}
} else {
return false;
}
} catch (error) {
console.log("에러발생");
......@@ -73,18 +82,26 @@ export default function Posting() {
}
}
function postingFormMatch() {
function postingFormMatch(user: PostType, imgSrc: string[]) {
if (!isLength(user.title ?? "", { min: 1 })) {
alert("제목을 입력해 주세요.");
setError("제목을 입력해 주세요.");
return false;
} else if (!isLength(user.text ?? "", { min: 1 })) {
alert("내용을 입력해 주세요.");
setError("내용을 입력해 주세요.");
return false;
} else if (equals(city, "질문종류")) {
} else if (equals(city, "city")) {
alert("도시를 선택해 주세요.");
setError("도시를 선택해 주세요.");
return false;
} else if (equals(theme, "theme")) {
alert("테마를 선택해 주세요.");
setError("테마를 선택해 주세요.");
return false;
} else if (equals(theme, "질문종류")) {
setError("도시를 선택해 주세요.");
} else if (imgSrc === undefined || imgSrc === null) {
alert("사진을 첨부해 주세요.");
setError("사진을 첨부해 주세요.");
return false;
} else {
return true;
......@@ -124,10 +141,13 @@ export default function Posting() {
};
const handleInputPic = async (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
const maxImg = 10;
const { files } = event.target;
if (!(files === null)) {
setFile(files);
}
if (!(files?.length === undefined)) {
if (files?.length <= maxImg) {
for (var i = 0; i < files.length; i++) {
......@@ -140,89 +160,105 @@ export default function Posting() {
};
}
} else {
alert("사진은 최대 10장까지 업로드 가능합니다!");
alert(`사진은 최대 ${maxImg}장까지 업로드 가능합니다!`);
}
}
};
return (
<div className="flex flex-col border-3 ">
<form onSubmit={handlePostSubmit} className="w-full items-center">
<div className="flex flex-row relative">
<p className="basis-1/12 gap-x-8">Id</p>
<p className="basis-6/12 invisible">empty</p>
<div className="basis-2/12 border-2 border-sky-300">
<input type="file" multiple onChange={handleInputPic} />
<div className="flex place-content-center">
<form
onSubmit={handlePostSubmit}
className="flex flex-col w-96 items-center"
>
<div className="flex flex-row h-8 gap-x-1">
<div className="flex border-2 border-sky-400 rounded-full w-20 place-content-center transition delay-150 bg-white-400 hover:-translate-y-1 hover:scale-110 hover:bg-gray-300 duration-300">
<input
id="files"
type="file"
multiple
onChange={handleInputPic}
className="hidden"
/>
<label htmlFor="files" className="text-xs text-center mt-1.5 ml-1 ">
파일 선택
</label>
</div>
<div className="flex w-20">
<select
name="city"
className="border-2 border-sky-400 rounded-full w-20 text-xs"
onChange={cityChange}
defaultValue="질문종류"
>
<option value="질문종류">도시</option>
<option value="Seoul">서울</option>
<option value="Busan">부산</option>
<option value="Incheon">인천</option>
<option value="Daegu">대구</option>
<option value="Gwangju">광주</option>
<option value="Daejeon">대전</option>
<option value="Woolsan">울산</option>
<option value="Sejong">세종</option>
<option value="Dokdo">독도</option>
<option value="Jeju">제주</option>
</select>
</div>
<div className="flex w-20">
<select
name="theme"
className="border-2 border-sky-400 rounded-full w-20 text-xs"
onChange={themeChange}
defaultValue="질문종류"
>
<option value="질문종류">테마</option>
<option value="cycling">사이클링</option>
<option value="surfing">서핑</option>
<option value="activity">액티비티</option>
<option value="camping">캠핑</option>
<option value="sking">스키</option>
<option value="boat">보트</option>
<option value="desert">사막</option>
<option value="golf">골프</option>
<option value="cave">동굴</option>
<option value="history">문화재</option>
<option value="zoo">동물원</option>
<option value="cycling">사이클링</option>
</select>
</div>
<div className="flex w-20">
<button
type="submit"
className="border-2 border-sky-400 rounded-full w-20 text-xs text-center transition ease-in-out delay-150 bg-white-400 hover:-translate-y-1 hover:scale-110 hover:bg-sky-300 duration-300"
>
글쓰기
</button>
</div>
<select
name="city"
id="Questions"
className="border-2 border-sky-300 w-1/12"
onChange={cityChange}
defaultValue="질문종류"
>
<option value="질문종류">도시</option>
<option value="Seoul">서울</option>
<option value="Busan">부산</option>
<option value="Incheon">인천</option>
<option value="Daegoo">대구</option>
<option value="Kwangjoo">광주</option>
<option value="Daejeon">대전</option>
<option value="Woolsan">울산</option>
<option value="Sejong">세종</option>
<option value="Dokdo">독도</option>
<option value="Jeju">제주</option>
</select>
<select
name="theme"
id="Questions"
className="border-2 border-sky-300 w-1/12"
onChange={themeChange}
defaultValue="질문종류"
>
<option value="질문종류">테마</option>
<option value="cycling">사이클링</option>
<option value="surfing">서핑</option>
<option value="activity">액티비티</option>
<option value="camping">캠핑</option>
<option value="sking">스키</option>
<option value="boat">보트</option>
<option value="desert">사막</option>
<option value="golf">골프</option>
<option value="cave">동굴</option>
<option value="history">문화재</option>
<option value="zoo">동물원</option>
<option value="cycling">사이클링</option>
</select>
<button
type="submit"
className="border-2 border-sky-300 basis-1/12 gap-x-8"
>
글쓰기
</button>
</div>
<div className="flex">
<div className="flex flex-col">
<textarea
name="title"
onChange={titleChange}
placeholder="title"
className="w-full h-8 border-2 border-sky-300"
></textarea>
</div>
<div className="flex flex-col">
<div className="flex h-48 overflow-x-scroll">
{imgSrc?.map((img, i) => (
<img key={i} src={img} width={200} height={200} />
))}
placeholder="제목을 입력해 주세요!"
className="flex w-96 border-2 border-sky-500 rounded"
/>
<div className="flex flex-col mt-1 mb-1">
<div className="flex gap-x-2 h-48 overflow-x-scroll">
{imgSrc?.map((img, i) => (
<img key={i} src={img} width={200} height={200} />
))}
</div>
</div>
<textarea
onChange={textChange}
name="text"
placeholder="text"
className="w-full h-96 border-2 border-sky-300"
></textarea>
placeholder="여행 후기를 알려주세요!"
className="flex w-96 h-96 border-2 border-sky-500 rounded"
/>
</div>
</form>
</div>
......
......@@ -16,7 +16,7 @@ export interface PostType {
text: string;
theme: string;
city: string;
date: string | number;
date: string;
counts: number;
_id: string;
user: string;
......
import formidable from "formidable";
import { asyncWrap } from "../helpers/asyncWrap";
import { TypedRequest } from "../types";
export const uploadFile = asyncWrap(async (reqExp, res, next) => {
const req = reqExp as TypedRequest;
const form = formidable({ multiples: false, uploadDir: "uploads" });
await new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
console.log("fields", fields);
console.log("files", files);
req.body = fields;
req.files = files;
resolve(files);
});
});
next();
return;
});
export const uploadFiles = asyncWrap(async (reqExp, res, next) => {
const req = reqExp as TypedRequest;
const form = formidable({ multiples: true, uploadDir: "uploads" });
await new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
console.log("fields", fields);
console.log("files", files);
req.body = fields;
req.files = files;
resolve(files);
});
});
next();
return;
});
export * as authCtrl from "./auth.controller";
export * as fileInfoCtrl from "./fileinfo.controller";
export * as mainimgCtrl from "./mainimg.controller";
export * as postCtrl from "./post.controller";
export * as roleCtrl from "./role.controller";
export * as userCtrl from "./user.controller";
export * as mainimgCtrl from "./mainimg.controller";
\ No newline at end of file
import { NextFunction, Request, Response } from "express";
import isLength from "validator/lib/isLength";
import equals from "validator/lib/equals";
import formidable, { Fields, Files } from "formidable";
import { TypedRequestAuth } from "./auth.controller";
import { asyncWrap } from "../helpers";
import { postDb, userDb } from "../db";
import { TypedRequest } from "../types";
export const postCreate = asyncWrap(async (reqExp, res, next) => {
export const createFileAndPost = asyncWrap(async (reqExp, res, next) => {
const req = reqExp as TypedRequestAuth<{ userId: string }>;
const { title, text, theme, city, date } = req.body as {
title: string;
text: string;
theme: string;
city: string;
date: Date;
counts: number;
};
// 1) title 빈 문자열인지 확인
if (!isLength(title ?? "", { min: 1 })) {
return res.status(422).send("제목을 한 글자 이상 입력해주세요");
}
// 2) body 빈 문자열인지 확인
if (!isLength(text ?? "", { min: 1 })) {
return res.status(422).send("제목을 한 글자 이상 입력해주세요");
}
const { userId } = req.auth;
// 3) theme dropdown default-value "테마"일 경우 에러
if (equals(theme, "질문종류")) {
return res.status(422).send("테마를 입력해 주세요");
}
// 4) city dropdown default-value "도시"일 경우 에러
if (equals(city, "질문종류")) {
return res.status(422).send("도시를 선택해 주세요");
}
const userId = req.auth.userId;
const newPost = await postDb.createPost({
title,
text,
theme,
city,
date: Date.now(),
user: userId,
const form = formidable({
uploadDir: "uploads",
keepExtensions: true,
multiples: true,
});
console.log("post", newPost);
return res.json(newPost);
const fileIdArr = new Array();
form.parse(req, async (err, fields, files) => {
if (!Array.isArray(fields.title)) {
const title = fields.title;
if (!Array.isArray(fields.text)) {
const text = fields.text;
if (!Array.isArray(fields.theme)) {
const theme = fields.theme;
if (!Array.isArray(fields.city)) {
const city = fields.city;
if (Array.isArray(files.picture)) {
for (var i = 0; i < files.picture.length; i++) {
const originalfilename = files.picture?.[i].originalFilename;
const newfilename = files.picture?.[i].newFilename;
const filepath = files.picture?.[i].filepath;
const filesRes = await postDb.createFilesRow(
originalfilename,
newfilename,
filepath
);
fileIdArr.push(filesRes);
}
} else if (!Array.isArray(files.picture)) {
const originalfilename = files.picture.originalFilename;
const newfilename = files.picture.newFilename;
const filepath = files.picture.filepath;
const filesRes = await postDb.createFilesRow(
originalfilename,
newfilename,
filepath
);
fileIdArr.push(filesRes);
} // file or one or more
const postRes = await postDb.createPostRow({
title,
text,
theme,
city,
date: Date.now(),
counts: 0,
user: userId,
file: fileIdArr,
});
}
}
}
}
});
});
export const getAllPost = asyncWrap(async (req, res) => {
......@@ -61,13 +80,20 @@ export const getAllPost = asyncWrap(async (req, res) => {
return res.json(posts);
});
export const getFiles = asyncWrap(async (req, res) => {
const { postId } = req.params;
// console.log("나는 말하는 고구마.", postId);
const files = await postDb.getFilesByPostId(postId);
return res.json(files);
});
export const addCounts = asyncWrap(async (req, res) => {
// console.log(req.body);
const { postId } = req.params;
const { counts } = req.body as {
counts: number;
};
console.log(postId, counts);
// console.log(postId, counts);
const updateCounts = await postDb.addOneCount(postId, counts);
......@@ -128,7 +154,7 @@ export const updatePost = asyncWrap(async (reqExp, res) => {
postId
);
console.log("게시글 수정 후", updatePost);
// console.log("게시글 수정 후", updatePost);
return res.json(updatePost);
});
import { Types, ObjectId } from "mongoose";
import { Post, PostType } from "../models";
import { FileInfo, IFileInfo } from "../models";
export const createPost = async (post: PostType) => {
const newPosting = await Post.create({
export const createPostRow = async (post: PostType) => {
const newPostRow = await Post.create({
title: post.title,
text: post.text,
theme: post.theme,
......@@ -9,23 +11,41 @@ export const createPost = async (post: PostType) => {
user: post.user,
date: post.date,
counts: 0,
file: post.file,
});
return newPosting;
return newPostRow;
};
export const createFilesRow = async (
originalfilename?: string | null,
newfilename?: string,
picturepath?: string
) => {
const newFileRow = await FileInfo.create({
originalfilename: originalfilename,
newfilename: newfilename,
picturepath: picturepath,
});
// console.log("check", newFileRow);
return newFileRow._id;
};
export const getPosts = async () => {
const posts = await Post.find({});
const posts = await Post.find({}).sort({ date: -1 });
return posts;
};
export const getFilesByPostId = async (postId: string) => {
const files = await Post.findOne({ _id: postId }).populate("file");
return files?.file;
};
export const addOneCount = async (_id: string, counts: number) => {
const newCounts = await Post.findOneAndUpdate(
{ _id: _id },
{ counts: counts },
{ new: true }
);
// console.log(newCounts);
return newCounts;
};
......
import bcrypt from "bcryptjs";
import { ObjectId } from "mongoose";
import { IUser, Role, Post, User, Avatar } from "../models";
import { IUser, Role, Post, User, FileInfo } from "../models";
import fs from "fs/promises";
export const createUser = async (user: IUser) => {
// 비밀번호 암호화
const hash = await bcrypt.hash(user.password, 10);
const newAvatar = await Avatar.create({});
const newAvatar = await FileInfo.create({});
// 사용자 역할 추가: 기본값은 "user"
let userRole = null;
if (user.role) {
......@@ -82,17 +82,17 @@ export const postPicture = async (
if (!(profile?.avatar === undefined)) {
if (originalfilename === null) {
await Avatar.findByIdAndUpdate(profile.avatar._id, {
await FileInfo.findByIdAndUpdate(profile.avatar._id, {
nickname: nickname,
});
} else if (nickname === "") {
await Avatar.findByIdAndUpdate(profile.avatar._id, {
await FileInfo.findByIdAndUpdate(profile.avatar._id, {
originalfilename: originalfilename,
newfilename: newfilename,
picturepath: picturepath,
});
} else {
await Avatar.findByIdAndUpdate(profile.avatar._id, {
await FileInfo.findByIdAndUpdate(profile.avatar._id, {
originalfilename: originalfilename,
newfilename: newfilename,
picturepath: picturepath,
......@@ -104,10 +104,10 @@ export const postPicture = async (
export const deleteUser = async (userId: ObjectId) => {
const user = await User.findById(userId);
if (!(user?.avatar === undefined)) {
const ref = await Avatar.findById(user.avatar._id);
await fs.unlink("../travel/uploads/" + ref?.newfilename);
await Avatar.deleteOne({ _id: user.avatar._id });
await User.deleteOne({ _id: userId });
if (user && user.avatar) {
const file = await FileInfo.findById(user.avatar._id);
await fs.unlink("../travel/uploads/" + file?.newfilename);
await FileInfo.deleteOne({ _id: user.avatar._id });
return await user.deleteOne();
}
};
import { model, Schema } from "mongoose";
import { model, ObjectId, Schema } from "mongoose";
export interface IAvatar {
export interface IFileInfo {
originalfilename?: string;
newfilename?: string;
picturepath?: string;
nickname?: string;
}
const Avatarschema = new Schema<IAvatar>({
originalfilename: { type: String, unique: true },
const schema = new Schema<IFileInfo>({
originalfilename: { type: String },
newfilename: { type: String },
nickname: { type: String },
picturepath: { type: String },
});
export default model<IAvatar>("Avatar", Avatarschema);
export default model<IFileInfo>("FileInfo", schema);
export { default as User, IUser } from "./user.model";
export { default as Post, PostType } from "./post.model";
export { default as Role } from "./role.model";
export { default as Avatar, IAvatar } from "./fileinfo.model";
export { default as FileInfo, IFileInfo } from "./fileinfo.model";
export { default as Mainimg, MainimgType } from "./mainimg.model";
......@@ -5,9 +5,10 @@ export interface PostType {
text: string;
theme: string;
city: string;
user: Types.ObjectId | string;
date: Date | number;
counts?: number;
user: Types.ObjectId | string;
file?: Array<Types.ObjectId>;
}
const PostSchema = new Schema<PostType>({
......@@ -37,6 +38,12 @@ const PostSchema = new Schema<PostType>({
type: Number,
default: 0,
},
file: [
{
type: Schema.Types.ObjectId,
ref: "FileInfo",
},
],
});
export default model<PostType>("Post", PostSchema);
......@@ -12,6 +12,5 @@ router.use("/auth", authRouter);
router.use("/posts", postRouter);
router.use("/profile", profileRouter);
router.use("/mainimg", mainimgRouter);
//posting함수 -> mongodb에 posts json형식으로 저장
export default router;
import express from "express";
import { postCtrl, authCtrl } from "../controllers";
import { postCtrl, authCtrl, fileInfoCtrl } from "../controllers";
const router = express.Router();
router.route("/").post(authCtrl.requireLogin, postCtrl.postCreate);
router.route("/").post(authCtrl.requireLogin, postCtrl.createFileAndPost);
router.route("/").get(postCtrl.getAllPost);
router.route("/files/:postId").get(authCtrl.requireLogin, postCtrl.getFiles);
// router.param("postId", postCtrl.userByPostId);
router
.route("/:postId")
.post(authCtrl.requireLogin, postCtrl.addCounts)
.get(authCtrl.requireLogin, postCtrl.getOnePost)
.delete(authCtrl.requireLogin, postCtrl.deleteOnePost) // authenticate
.delete(authCtrl.requireLogin, postCtrl.deleteOnePost) // +authenticate
.put(authCtrl.requireLogin, postCtrl.updatePost);
router.param("postId", postCtrl.userByPostId);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment