Commit d1f8541e authored by Kim, MinGyu's avatar Kim, MinGyu
Browse files

admin 페이지 개선

parent 39e4b1fd
...@@ -2,4 +2,5 @@ node_modules/ ...@@ -2,4 +2,5 @@ node_modules/
.vscode/ .vscode/
package-lock.json package-lock.json
dist/ dist/
uploads/ uploads/
\ No newline at end of file adminpics
\ No newline at end of file
...@@ -2,7 +2,15 @@ import React from "react"; ...@@ -2,7 +2,15 @@ import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom"; import { BrowserRouter, Route, Routes } from "react-router-dom";
import "tailwindcss/tailwind.css"; import "tailwindcss/tailwind.css";
import { IntoPost } from "./post/intopost"; import { IntoPost } from "./post/intopost";
import { Login, Profile, RequireAuth, Signup, Admin, ImgRewrite } from "./auth"; import {
Login,
Profile,
RequireAuth,
Signup,
Admin,
ImgRewrite,
Search,
} from "./auth";
import { Header, Body } from "./home"; import { Header, Body } from "./home";
import { Board } from "./board"; import { Board } from "./board";
import Posting from "./post/posting"; import Posting from "./post/posting";
...@@ -39,7 +47,8 @@ export const App = () => { ...@@ -39,7 +47,8 @@ export const App = () => {
} }
/> />
<Route path="admin" element={<Admin />} /> <Route path="admin" element={<Admin />} />
<Route path="rewrite" element={<ImgRewrite/>}/> <Route path="rewrite" element={<ImgRewrite />} />
<Route path="search" element={<Search />} />
</Route> </Route>
</Route> </Route>
</Routes> </Routes>
......
...@@ -2,17 +2,17 @@ import axios from "axios"; ...@@ -2,17 +2,17 @@ import axios from "axios";
import { MainimgType } from "../types"; import { MainimgType } from "../types";
import baseUrl from "./baseUrl"; import baseUrl from "./baseUrl";
export const mainimg = async (mainimg: MainimgType) => { export const mainimg = async (formdata: FormData) => {
const { data } = await axios.post(`${baseUrl}/mainimg`, mainimg); const { data } = await axios.post(`${baseUrl}/mainimg`, formdata);
return data; return data;
}; };
export const delmainimg = async (_id : string) => { export const delmainimg = async (_id: string) => {
const { data } = await axios.delete(`${baseUrl}/mainimg/${_id}`); const { data } = await axios.delete(`${baseUrl}/mainimg/${_id}`);
return data; return data;
}; };
export const getmainimg = async () => { export const getmainimg = async () => {
const { data } = await axios.get(`${baseUrl}/mainimg`); const { data } = await axios.get(`${baseUrl}/mainimg`);
return data; return data;
}; };
import React, { FormEvent, useEffect, useState, MouseEvent } from "react"; import React, { FormEvent, useEffect, useState, MouseEvent } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { mainimgApi } from "../apis"; import { mainimgApi } from "../apis";
import { picture } from "../apis/profile.api";
import { catchErrors } from "../helpers"; import { catchErrors } from "../helpers";
import { MainimgType } from "../types"; import { MainimgType } from "../types";
import { MySlide } from "./adminslide"; import { MySlide } from "./adminslide";
export default function Admin() { export default function Admin() {
// 이미지 전체 불러오기 // 이미지 전체 불러오기
const [getimgs, setGetimgs] = useState<MainimgType[]>([]); const [getimgs, setGetimgs] = useState<MainimgType[]>([]);
async function imgsData() { async function imgsData() {
const imgs = await mainimgApi.getmainimg(); const imgs = await mainimgApi.getmainimg();
setGetimgs(imgs) setGetimgs(imgs);
}; }
useEffect(() => { useEffect(() => {
imgsData(); imgsData();
}, []); }, []);
console.log(getimgs);
// 이미지 추가하기
const [addimg, setAddimg] = useState<MainimgType>({
_id: "",
theme: "",
city: "",
title: "",
pic: { originalfilename: "", newfilename: "" },
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [addSuccess, setAddSuccess] = useState(false);
const [delSuccess, setDelSuccess] = useState(false);
const [file, setFile] = useState<File>();
// 이미지 추가하기 function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
const [addimg, setAddimg] = useState<MainimgType>({ const { name, value } = event.currentTarget;
_id: "", console.log(value);
theme: "", setAddimg({ ...addimg, [name]: value });
city: "", }
url: "",
title: "",
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [addSuccess, setAddSuccess] = useState(false);
const [delSuccess, setDelSuccess] = useState(false);
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) { function handleInputeChange(event: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = event.currentTarget; const { name, value } = event.currentTarget;
setAddimg({ ...addimg, [name]: value }); setAddimg({ ...addimg, [name]: value });
} }
function handleInputeChange(event: React.ChangeEvent<HTMLInputElement>) { function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
const { name, value } = event.currentTarget; const file = e.target.files?.[0];
setAddimg({ ...addimg, [name]: value }); if (!(file === undefined)) {
setFile(file);
} }
}
async function handleSubmit(event: FormEvent) { async function handleSubmit(event: FormEvent) {
event.preventDefault(); event.preventDefault();
try {
setError("");
console.log("img data", addimg);
setLoading(true);
const res = await mainimgApi.mainimg(addimg);
console.log("서버연결됬나요", res);
setAddSuccess(true);
setError("");
} catch (error) {
console.log("에러발생");
catchErrors(error, setError);
} finally {
setLoading(false);
}
}
if (addSuccess) { const formdata = new FormData();
alert("img 추가되었습니다"); console.log(addimg);
formdata.append("city", addimg.city);
formdata.append("theme", addimg.theme);
formdata.append("title", addimg.title);
if (!(file === undefined)) {
formdata.append("mainimg", file);
console.log(formdata);
const res = await mainimgApi.mainimg(formdata);
console.log("확인 중 ", res);
} }
// 이미지 삭제하기 }
async function handleDeleteClick(event: MouseEvent<HTMLButtonElement>) {
try {
if (confirm("삭제하시겠습니까?") == true) {
const picId = event.currentTarget.id;
console.log("picId : ", picId)
const res = await mainimgApi.delmainimg(picId);
console.log("delete img", res);
setDelSuccess(true);
setError("");
} else {
return false;
}
}
catch (error) {
console.log("에러발생");
catchErrors(error, setError);
} finally {
setLoading(false);
};
};
if (delSuccess) {
alert("img 삭제되었습니다");
}
let limit = 15;
const numPages = Math.ceil(getimgs.length / 15);
const slides = [] if (addSuccess) {
for (let i = 0; i < numPages; i++) { alert("img 추가되었습니다");
const k = [ }
getimgs.slice(i * limit, i * limit + limit).map((pic, index: number) => ( // 이미지 삭제하기
<div key={index}> async function handleDeleteClick(event: MouseEvent<HTMLButtonElement>) {
<div className={`m-1 shrink-0 bg-gray-200 rounded shadow-md `}> try {
<img if (confirm("삭제하시겠습니까?") == true) {
src={pic.url} const picId = event.currentTarget.id;
className="w-full h-10 md:h-20 object-center" console.log("picId : ", picId);
/> const res = await mainimgApi.delmainimg(picId);
<p className="text-center text-xs">{pic.title}</p> console.log("delete img", res);
</div> setDelSuccess(true);
<div className="text-end"> setError("");
<button className="border-r-2 border-r-indigo-500 text-xs"> } else {
<Link to="/rewrite"> return false;
수정 }
</Link> } catch (error) {
</button> console.log("에러발생");
<button id={pic._id} onClick={handleDeleteClick} className="text-xs"> catchErrors(error, setError);
삭제 } finally {
</button> setLoading(false);
</div>
</div>
))]
slides.push(k);
} }
}
if (delSuccess) {
alert("img 삭제되었습니다");
}
return ( let limit = 15;
<div> const numPages = Math.ceil(getimgs.length / 15);
<form
onSubmit={handleSubmit}>
<div className="flex flex-wrap justify-center gap-3">
<div className="gap-3 md:flex ">
<select
name="city"
id="Questions"
className="border border-3 border-black w-20 my-5"
defaultValue="질문종류"
onChange={handleSelectChange}
>
<option value="질문종류">도시</option>
<option value="서울">서울</option>
<option value="부산">부산</option>
<option value="인천">인천</option>
<option value="대구">대구</option>
<option value="광주">광주</option>
<option value="대전">대전</option>
<option value="울산">울산</option>
<option value="세종">세종</option>
<option value="독도">독도</option>
<option value="제주">제주</option>
</select>
<select
name="theme"
id="Questions"
className="border border-3 border-black w-20 my-5"
defaultValue="질문종류"
onChange={handleSelectChange}
>
<option value="질문종류">테마</option>
<option value="사이클링">사이클링</option>
<option value="서핑">서핑</option>
<option value="액티비티">액티비티</option>
<option value="캠핑">캠핑</option>
<option value="스키">스키</option>
<option value="보트">보트</option>
<option value="사막">사막</option>
<option value="골프">골프</option>
<option value="동굴">동굴</option>
<option value="문화재">문화재</option>
<option value="동물원">동물원</option>
<option value="사이클링">사이클링</option>
</select>
<div className="flex items-center justify-end gap-3">
<p>url :</p>
<input name="url" className="border-2 border-sky-500"
onChange={handleInputeChange} />
{/* type="file"/> */}
</div>
<div className="flex items-center justify-end gap-3 mt-2 md:mt-0">
<p>title :</p>
<input name="title" className="border-2 border-sky-500"
onChange={handleInputeChange} />
</div>
</div>
<div className="my-5 flex items-center">
<button className="border-2 border-gray-500 rounded ">추가</button>
</div>
</div>
</form>
<div className="flex justify-center">
<MySlide key={Math.random()}
slides={slides}
/>
const slides = [];
for (let i = 0; i < numPages; i++) {
const k = [
getimgs
.slice(i * limit, i * limit + limit)
.map((picture, index: number) => (
<div key={index}>
<div className={`m-1 shrink-0 bg-gray-200 rounded shadow-md `}>
<img
src={"http://localhost:3000/images/" + picture.pic.newfilename}
className="w-full h-10 md:h-20 object-center"
/>
<p className="text-center text-xs">{picture.title}</p>
</div>
<div className="text-end">
<button className="border-r-2 border-r-indigo-500 text-xs">
<Link to="/rewrite">수정</Link>
</button>
<button
id={picture._id}
onClick={handleDeleteClick}
className="text-xs"
>
삭제
</button>
</div>
</div>
)),
];
slides.push(k);
}
return (
<div>
<form onSubmit={handleSubmit}>
<div className="flex flex-wrap justify-center gap-3">
<div className="gap-3 md:flex ">
<select
name="city"
id="Questions"
className="border border-3 border-black w-20 my-5"
defaultValue="질문종류"
onChange={handleSelectChange}
>
<option value="질문종류">도시</option>
<option value="서울">서울</option>
<option value="부산">부산</option>
<option value="인천">인천</option>
<option value="대구">대구</option>
<option value="광주">광주</option>
<option value="대전">대전</option>
<option value="울산">울산</option>
<option value="세종">세종</option>
<option value="독도">독도</option>
<option value="제주">제주</option>
</select>
<select
name="theme"
id="Questions"
className="border border-3 border-black w-20 my-5"
defaultValue="질문종류"
onChange={handleSelectChange}
>
<option value="질문종류">테마</option>
<option value="사이클링">사이클링</option>
<option value="서핑">서핑</option>
<option value="액티비티">액티비티</option>
<option value="캠핑">캠핑</option>
<option value="스키">스키</option>
<option value="보트">보트</option>
<option value="사막">사막</option>
<option value="골프">골프</option>
<option value="동굴">동굴</option>
<option value="문화재">문화재</option>
<option value="동물원">동물원</option>
<option value="사이클링">사이클링</option>
</select>
<div className="flex items-center justify-end gap-3">
<input
type="file"
id="files"
className="hidden"
onChange={handleFileChange}
></input>
<label htmlFor="files" className="border-2 m-5">
이미지 선택
</label>
</div>
<div className="flex items-center justify-end gap-3 mt-2 md:mt-0">
<p>title :</p>
<input
name="title"
className="border-2 border-sky-500"
onChange={handleInputeChange}
/>
</div> </div>
</div>
<div className="my-5 flex items-center">
<button className="border-2 border-gray-500 rounded ">추가</button>
</div>
</div> </div>
); </form>
}; <div className="flex justify-center">
<MySlide key={Math.random()} slides={slides} />
</div>
</div>
);
}
...@@ -3,4 +3,5 @@ export { default as Signup } from "./signup"; ...@@ -3,4 +3,5 @@ export { default as Signup } from "./signup";
export { default as Profile } from "./profile"; export { default as Profile } from "./profile";
export { RequireAuth } from "./RequireAuth"; export { RequireAuth } from "./RequireAuth";
export { default as Admin } from "./admin"; export { default as Admin } from "./admin";
export {default as ImgRewrite} from "./imgrewrite" export { default as ImgRewrite } from "./imgrewrite";
export { default as Search } from "./search";
import React from "react";
import { useLocation } from "react-router-dom";
export default function Search() {
const a = useLocation().state;
return (
<div>
<div></div>
</div>
);
}
...@@ -6,6 +6,13 @@ import "tailwindcss/tailwind.css"; ...@@ -6,6 +6,13 @@ import "tailwindcss/tailwind.css";
export default function Header() { export default function Header() {
const { logout } = useAuth(); const { logout } = useAuth();
const [search, setSearch] = useState("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newvalue = e.target.value;
setSearch(newvalue);
};
return ( return (
<div className="flex flex-col "> <div className="flex flex-col ">
<div className="flex py-10 "> <div className="flex py-10 ">
...@@ -18,9 +25,12 @@ export default function Header() { ...@@ -18,9 +25,12 @@ export default function Header() {
<input <input
className="md:ml-20 placeholder:text-white focus:outline-none focus:border-y-4 focus:border-l-4 focus:border-sky-500 md:placeholder:text-slate-400 w-20 md:w-1/2 border-y-4 border-l-4 border-sky-300 pl-9 rounded-l-full focus:border-0" className="md:ml-20 placeholder:text-white focus:outline-none focus:border-y-4 focus:border-l-4 focus:border-sky-500 md:placeholder:text-slate-400 w-20 md:w-1/2 border-y-4 border-l-4 border-sky-300 pl-9 rounded-l-full focus:border-0"
placeholder="어디로 여행가고 싶나요?" placeholder="어디로 여행가고 싶나요?"
onChange={handleChange}
/> />
<button className="shrink-0 border-y-4 border-r-4 border-sky-500 rounded-r-full pr-4"> <button className="shrink-0 border-y-4 border-r-4 border-sky-500 rounded-r-full pr-4">
검색 {/* <Link to="/search" state={}>
검색
</Link> */}
</button> </button>
<div className="shrink-0 p-3 md:ml-40 h-12"> <div className="shrink-0 p-3 md:ml-40 h-12">
......
...@@ -43,6 +43,9 @@ export interface MainimgType { ...@@ -43,6 +43,9 @@ export interface MainimgType {
_id: string; _id: string;
theme: string; theme: string;
city: string; city: string;
url: string;
title: string; title: string;
pic: {
originalfilename: string;
newfilename: string;
};
} }
...@@ -12,6 +12,7 @@ app.use(cookieParser()); ...@@ -12,6 +12,7 @@ app.use(cookieParser());
app.use("/api", router); app.use("/api", router);
app.use("/images", express.static(path.join(__dirname, "..", "/uploads"))); app.use("/images", express.static(path.join(__dirname, "..", "/uploads")));
app.use("/images", express.static(path.join(__dirname, "..", "/adminpics")));
app.use((err: any, req: Request, res: Response, next: NextFunction) => { app.use((err: any, req: Request, res: Response, next: NextFunction) => {
console.log("익스프레스 에러: ", err); console.log("익스프레스 에러: ", err);
......
...@@ -4,35 +4,53 @@ import { TypedRequestAuth } from "./auth.controller"; ...@@ -4,35 +4,53 @@ import { TypedRequestAuth } from "./auth.controller";
import { asyncWrap } from "../helpers"; import { asyncWrap } from "../helpers";
import { mainimgDb } from "../db"; import { mainimgDb } from "../db";
import { TypedRequest } from "../types"; import { TypedRequest } from "../types";
import { ObjectId } from "mongoose";
import formidable from "formidable";
export const createMainimg = asyncWrap(async (reqExp, res, next) => { export const createMainimg = asyncWrap(async (reqExp, res) => {
const req = reqExp as TypedRequestAuth<{ userId: string }>; const req = reqExp as TypedRequestAuth<{ userId: ObjectId }>;
const { userId } = req.auth;
const { theme, city, url, title } = req.body as { const form = formidable({
theme: string; uploadDir: "adminpics",
city: string; keepExtensions: true,
url: string; multiples: false,
title: string; });
};
console.log("body", req.body);
if (!isLength(url ?? "", { min: 1 })) {
return res.status(422).send("이미지 url을 입력해주세요");
}
if (!isLength(title ?? "", { min: 1 })) { form.parse(req, (err, fields, files) => {
return res.status(422).send("이미지 제목을 입력해주세요"); if (!Array.isArray(files.mainimg)) {
} //파일 좁히기 중
if (
!(
Array.isArray(fields.city) ||
Array.isArray(fields.title) ||
Array.isArray(fields.theme)
)
) {
const city = fields.city;
const title = fields.title;
const theme = fields.theme;
const newMainimg = await mainimgDb.createMainimg({ // if (!isLength(title ?? "", { min: 1 })) {
theme, // return res.status(422).send("이미지 제목을 입력해주세요");
city, // }
url, console.log(files);
title, const originalfilename = files.mainimg?.originalFilename;
const newfilename = files.mainimg.newFilename;
if (!(originalfilename === null || newfilename === undefined)) {
mainimgDb.createMainimg(
{ city, title, theme },
{
originalfilename,
newfilename,
}
);
}
}
}
}); });
return res.json(newMainimg); res.json();
}); });
export const getMainimg = asyncWrap(async (req, res) => { export const getMainimg = asyncWrap(async (req, res) => {
...@@ -40,7 +58,6 @@ export const getMainimg = asyncWrap(async (req, res) => { ...@@ -40,7 +58,6 @@ export const getMainimg = asyncWrap(async (req, res) => {
return res.json(mainimgs); return res.json(mainimgs);
}); });
export const deleteMainimg = asyncWrap(async (req, res) => { export const deleteMainimg = asyncWrap(async (req, res) => {
const { imgId } = req.params; const { imgId } = req.params;
console.log(imgId); console.log(imgId);
...@@ -48,6 +65,3 @@ export const deleteMainimg = asyncWrap(async (req, res) => { ...@@ -48,6 +65,3 @@ export const deleteMainimg = asyncWrap(async (req, res) => {
return res.json(deleteCount); return res.json(deleteCount);
}); });
import { userDb } from "../db"; import { userDb } from "../db";
import { asyncWrap } from "../helpers/asyncWrap"; import { asyncWrap } from "../helpers/asyncWrap";
import { Request } from "express"; import { Request } from "express";
import formidable, { Fields, Files } from "formidable"; import formidable from "formidable";
import { ObjectId } from "mongoose"; import { ObjectId } from "mongoose";
import fs from "fs"; import fs from "fs";
......
import { Mainimg, MainimgType } from "../models"; import { ObjectId } from "mongoose";
import { Avatar, IAvatar, Mainimg, MainimgType } from "../models";
export const createMainimg = async (mainimg: MainimgType, pic: IAvatar) => {
const newPic = await Avatar.create({
originalfilename: pic.originalfilename,
newfilename: pic.newfilename,
pictureauth: pic.picturepath,
});
export const createMainimg = async (mainimg: MainimgType) => {
const newMainimg = await Mainimg.create({ const newMainimg = await Mainimg.create({
theme: mainimg.theme, theme: mainimg.theme,
city: mainimg.city, city: mainimg.city,
url: mainimg.url, pic: newPic._id,
title: mainimg.title, title: mainimg.title,
}); });
return newMainimg; return newMainimg;
}; };
export const getMainimg = async () => { export const getMainimg = async () => {
const users = await Mainimg.find({}); const img = await Mainimg.find({}).populate("pic");
return users;
}; return img;
};
export const deleteOneMainimg = async (_id: string) => { export const deleteOneMainimg = async (_id: string) => {
const res = await Mainimg.deleteOne({ _id: _id }); const res = await Mainimg.deleteOne({ _id: _id });
......
import bcrypt from "bcryptjs"; import bcrypt from "bcryptjs";
import { ObjectId } from "mongoose"; import { ObjectId } from "mongoose";
import { IUser, Role, Post, User, Avatar } from "../models"; import { IUser, Role, Post, User, Avatar } from "../models";
import fs from "fs"; import fs from "fs/promises";
export const createUser = async (user: IUser) => { export const createUser = async (user: IUser) => {
// 비밀번호 암호화 // 비밀번호 암호화
...@@ -106,9 +106,7 @@ export const deleteUser = async (userId: ObjectId) => { ...@@ -106,9 +106,7 @@ export const deleteUser = async (userId: ObjectId) => {
const user = await User.findById(userId); const user = await User.findById(userId);
if (!(user?.avatar === undefined)) { if (!(user?.avatar === undefined)) {
const ref = await Avatar.findById(user.avatar._id); const ref = await Avatar.findById(user.avatar._id);
fs.unlink("../travel/uploads/" + ref?.newfilename, (err) => { await fs.unlink("../travel/uploads/" + ref?.newfilename);
console.log(err);
});
await Avatar.deleteOne({ _id: user.avatar._id }); await Avatar.deleteOne({ _id: user.avatar._id });
await User.deleteOne({ _id: userId }); await User.deleteOne({ _id: userId });
} }
......
import {model, Schema } from "mongoose"; import { model, Schema, Types } from "mongoose";
export interface MainimgType { export interface MainimgType {
theme: string;
theme: string; city: string;
city: string; title: string;
url: string; pic?: Types.ObjectId;
title: string;
} }
const MainimgSchema = new Schema<MainimgType>({ const MainimgSchema = new Schema<MainimgType>({
theme: { theme: {
type: String, type: String,
}, },
city: { city: {
type: String, type: String,
}, },
url : { title: {
type: String, type: String,
}, required: true,
title: { },
type: String, pic: { type: Schema.Types.ObjectId, ref: "Avatar" },
required: true,
},
}); });
export default model<MainimgType>("Mainimg", MainimgSchema); export default model<MainimgType>("Mainimg", MainimgSchema);
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