From 0259721b5026674faac062798d302db86f20e15f Mon Sep 17 00:00:00 2001 From: malloc Date: Tue, 9 Apr 2024 23:27:53 +0900 Subject: [PATCH 1/3] task done --- client/src/components/Type.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/src/components/Type.tsx diff --git a/client/src/components/Type.tsx b/client/src/components/Type.tsx new file mode 100644 index 0000000..e69de29 From da780fe2b5f7633aa24c3bac739c032d3cd3558e Mon Sep 17 00:00:00 2001 From: malloc Date: Tue, 9 Apr 2024 23:29:29 +0900 Subject: [PATCH 2/3] real commit --- client/src/components/Type.tsx | 21 +++++ client/src/pages/account.tsx | 10 ++- client/src/pages/css/feed.css | 30 +++++++ client/src/pages/feed.tsx | 86 +++++++++++++++++--- seminar/.env.example | 3 +- seminar/package-lock.json | 22 ++++- seminar/package.json | 3 +- seminar/src/middleware/auth.js | 5 +- seminar/src/routes/feed.js | 144 ++++++++++++++++++++------------- 9 files changed, 249 insertions(+), 75 deletions(-) diff --git a/client/src/components/Type.tsx b/client/src/components/Type.tsx index e69de29..40a5897 100644 --- a/client/src/components/Type.tsx +++ b/client/src/components/Type.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +interface TypeDropDownProps { + value: string; + setSNewPostType: React.Dispatch>; + setIsOpen: React.Dispatch>; + isOpen: boolean; +} + +const Type: React.FC = ({ value, setSNewPostType, setIsOpen, isOpen }) => { + const handleValueClick = () => { + setSNewPostType(value); + setIsOpen(!isOpen); + }; + + return ( +
  • {value}
  • + ); +}; + +export default Type; \ No newline at end of file diff --git a/client/src/pages/account.tsx b/client/src/pages/account.tsx index 37341f5..4cf8e8f 100644 --- a/client/src/pages/account.tsx +++ b/client/src/pages/account.tsx @@ -5,14 +5,15 @@ import {SAPIBase} from "../tools/api"; import "./css/account.css"; const AccountPage = () => { - const [ SAPIKEY, setSAPIKEY ] = React.useState(""); + const [ username, setUsername ] = React.useState(""); + const [ pw, setPw ]=React.useState(""); const [ NBalance, setNBalance ] = React.useState("Not Authorized"); const [ NTransaction, setNTransaction ] = React.useState(0); const getAccountInformation = () => { const asyncFun = async() => { interface IAPIResponse { balance: number }; - const { data } = await axios.post(SAPIBase + '/account/getInfo', { credential: SAPIKEY }); + const { data } = await axios.post(SAPIBase + '/account/getInfo', { credentialID: username, credentialPW: pw }); setNBalance(data.balance); } asyncFun().catch((e) => window.alert(`AN ERROR OCCURED: ${e}`)); @@ -22,7 +23,7 @@ const AccountPage = () => { const asyncFun = async() => { if (amount === '') return; interface IAPIResponse { success: boolean, balance: number, msg: string }; - const { data } = await axios.post(SAPIBase + '/account/transaction', { credential: SAPIKEY, amount: amount }); + const { data } = await axios.post(SAPIBase + '/account/transaction', { credentialID: username, credentialPW: pw, amount: amount }); setNTransaction(0); if (!data.success) { window.alert('Transaction Failed:' + data.msg); @@ -40,7 +41,8 @@ const AccountPage = () => {

    Account

    - Enter API Key: setSAPIKEY(e.target.value)}/> + Enter Username: setUsername(e.target.value)}/> + Enter Password: setPw(e.target.value)}/>
    diff --git a/client/src/pages/css/feed.css b/client/src/pages/css/feed.css index 53580df..a6df1ab 100644 --- a/client/src/pages/css/feed.css +++ b/client/src/pages/css/feed.css @@ -38,7 +38,37 @@ font-size: 18px; } +.edit-item { + position: absolute; + border: 1px solid black; + border-radius: 4px; + padding: 2px; + background-color: lightgray; + font-size: 12px; + right: 15px; + top: 50px; +} + +.edit-item:hover { + font-weight: bold; + cursor: pointer; +} + .delete-item:hover { font-weight: bold; cursor: pointer; +} + +ul { + list-style: none; +} + +.feed-type-dropdown { + display:inline; +} + +.type-dropdown-list:hover { + background-color: lightgray; + font-weight: bold; + cursor: pointer; } \ No newline at end of file diff --git a/client/src/pages/feed.tsx b/client/src/pages/feed.tsx index b64c53b..817aac9 100644 --- a/client/src/pages/feed.tsx +++ b/client/src/pages/feed.tsx @@ -2,15 +2,41 @@ import React from "react"; import axios from "axios"; import { SAPIBase } from "../tools/api"; import Header from "../components/header"; +import Type from "../components/Type"; import "./css/feed.css"; -interface IAPIResponse { id: number, title: string, content: string } +interface IAPIResponse { id: number, title: string, content: string, type: string } const FeedPage = (props: {}) => { const [ LAPIResponse, setLAPIResponse ] = React.useState([]); const [ NPostCount, setNPostCount ] = React.useState(0); const [ SNewPostTitle, setSNewPostTitle ] = React.useState(""); const [ SNewPostContent, setSNewPostContent ] = React.useState(""); + const [ SNewPostType, setSNewPostType] = React.useState(""); + + const dropDownRef = React.useRef(null); + const typeList:string[] =["BLOG", "INFO", "OTHERS"]; + const useTypeState = (ref: React.MutableRefObject) => { + const [isOpen, setIsOpen] = React.useState(false); + + React.useEffect(() => { + const pageClickEvent = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) { + setIsOpen(!isOpen); + } + }; + + if (isOpen) { + window.addEventListener('click', pageClickEvent); + } + + return () => { + window.removeEventListener('click', pageClickEvent); + }; + }, [isOpen, ref]); + return [isOpen, setIsOpen]; + } + const [isOpen, setIsOpen] = useTypeState(dropDownRef) as [boolean, React.Dispatch>];; React.useEffect( () => { let BComponentExited = false; @@ -27,10 +53,11 @@ const FeedPage = (props: {}) => { const createNewPost = () => { const asyncFun = async () => { - await axios.post( SAPIBase + '/feed/addFeed', { title: SNewPostTitle, content: SNewPostContent } ); + await axios.post( SAPIBase + '/feed/addFeed', { title: SNewPostTitle, content: SNewPostContent, type: SNewPostType } ); setNPostCount(NPostCount + 1); setSNewPostTitle(""); setSNewPostContent(""); + setSNewPostType(""); } asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`)); } @@ -44,6 +71,22 @@ const FeedPage = (props: {}) => { asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`)); } + const editPost = (id: string, newTitle: string, newContent: string, type: string) => { + const asyncFun = async () => { + await axios.post( SAPIBase + '/feed/editFeed', { id: id, title: newTitle, content: newContent, type: type } ); + const posts = LAPIResponse.map((tmp)=>{ + if (tmp.id===parseInt(id)) { + tmp.title=newTitle; + tmp.content=newContent; + tmp.type=type; + } + return tmp; + }); + setLAPIResponse(posts); + } + asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`)); + } + return (
    @@ -55,14 +98,39 @@ const FeedPage = (props: {}) => { />
    - { LAPIResponse.map( (val, i) => -
    -
    deletePost(`${val.id}`)}>ⓧ
    -

    { val.title }

    -

    { val.content }

    -
    - ) } + {LAPIResponse.map((val, i) => +
    +
    {val.type}
    +
    deletePost(`${val.id}`)}>ⓧ
    +
    { + const newTitle = window.prompt("Enter new title: ", val.title); + const newContent = window.prompt("Enter new content: ", val.content); + if (newTitle && newContent) editPost(`${val.id}`, newTitle, newContent, `${val.type}`); + }}>Edit +
    +

    {val.title}

    +

    {val.content}

    +
    + )}
    + Category:   +
    + setIsOpen((open: boolean) => !open)} + type='button' + value={SNewPostType} + /> + ... + {isOpen && +
      + {typeList.map((value, index) => ( + + ))} +
    + } +
    +
    Title: setSNewPostTitle(e.target.value)}/>      Content: setSNewPostContent(e.target.value)}/> diff --git a/seminar/.env.example b/seminar/.env.example index f84e5b2..615000a 100644 --- a/seminar/.env.example +++ b/seminar/.env.example @@ -1,3 +1,4 @@ PORT=8080 NODE_ENV=DEVELOPMENT -API_KEY= \ No newline at end of file +USERNAME="sample" +PASSWORD="sample" diff --git a/seminar/package-lock.json b/seminar/package-lock.json index 7118cb7..21b17bb 100644 --- a/seminar/package-lock.json +++ b/seminar/package-lock.json @@ -12,7 +12,8 @@ "cors": "^2.8.5", "dotenv": "^16.0.0", "ejs": "^3.1.7", - "express": "^4.17.3" + "express": "^4.17.3", + "prettier": "^3.2.5" }, "devDependencies": { "nodemon": "^2.0.15" @@ -1329,6 +1330,20 @@ "node": ">=4" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2850,6 +2865,11 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/seminar/package.json b/seminar/package.json index c7b1c5c..d6949d2 100644 --- a/seminar/package.json +++ b/seminar/package.json @@ -14,7 +14,8 @@ "cors": "^2.8.5", "dotenv": "^16.0.0", "ejs": "^3.1.7", - "express": "^4.17.3" + "express": "^4.17.3", + "prettier": "^3.2.5" }, "devDependencies": { "nodemon": "^2.0.15" diff --git a/seminar/src/middleware/auth.js b/seminar/src/middleware/auth.js index 480f41d..ca4327c 100644 --- a/seminar/src/middleware/auth.js +++ b/seminar/src/middleware/auth.js @@ -1,9 +1,12 @@ +require('dotenv').config() + const authMiddleware = (req, res, next) => { - if (req.body.credential === process.env.API_KEY) { + if (req.body.credentialID === process.env.id && req.body.credentialPW === process.env.pw) { console.log("[AUTH-MIDDLEWARE] Authorized User"); next(); } else { + console.log(process.env.id, process.env.pw); console.log("[AUTH-MIDDLEWARE] Not Authorized User"); res.status(401).json({ error: "Not Authorized" }); } diff --git a/seminar/src/routes/feed.js b/seminar/src/routes/feed.js index f9c19d9..f3f021a 100644 --- a/seminar/src/routes/feed.js +++ b/seminar/src/routes/feed.js @@ -1,76 +1,104 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); class FeedDB { - static _inst_; - static getInst = () => { - if ( !FeedDB._inst_ ) FeedDB._inst_ = new FeedDB(); - return FeedDB._inst_; - } + static _inst_; + static getInst = () => { + if (!FeedDB._inst_) FeedDB._inst_ = new FeedDB(); + return FeedDB._inst_; + }; - #id = 1; #itemCount = 1; #LDataDB = [{ id: 0, title: "test1", content: "Example body" }]; + #id = 1; + #itemCount = 0; + #LDataDB = []; - constructor() { console.log("[Feed-DB] DB Init Completed"); } + constructor() { + console.log("[Feed-DB] DB Init Completed"); + } - selectItems = ( count ) => { - if (count > this.#itemCount) return { success: false, data: "Too many items queried" }; - if (count < 0) return { success: false, data: "Invalid count provided" }; - else return { success: true, data: this.#LDataDB.slice(0, count) } - } + selectItems = (count) => { + if (count > this.#itemCount) + return { success: false, data: "Too many items queried" }; + if (count < 0) return { success: false, data: "Invalid count provided" }; + else return { success: true, data: this.#LDataDB.slice(0, count) }; + }; - insertItem = ( item ) => { - const { title, content } = item; - this.#LDataDB.push({ id: this.#id, title, content }); - this.#id++; this.#itemCount++; - return true; - } + insertItem = (item) => { + const { title, content, type } = item; + this.#LDataDB.push({ id: this.#id, title, content, type }); + this.#id++; + this.#itemCount++; + return true; + }; - deleteItem = ( id ) => { - let BItemDeleted = false; - this.#LDataDB = this.#LDataDB.filter((value) => { - const match = (value.id === id); - if (match) BItemDeleted = true; - return !match; - }); - if (BItemDeleted) id--; - return BItemDeleted; - } + deleteItem = (id) => { + let BItemDeleted = false; + this.#LDataDB = this.#LDataDB.filter((value) => { + const match = value.id === id; + if (match) BItemDeleted = true; + return !match; + }); + if (BItemDeleted) id--; + return BItemDeleted; + }; + + editItem = (id, title, content, type) => { + this.#LDataDB = this.#LDataDB.map((tmp) => { + if (tmp.id === id) { + tmp.title = title; + tmp.content = content; + tmp.type = type; + } + return tmp; + }); + }; } const feedDBInst = FeedDB.getInst(); -router.get('/getFeed', (req, res) => { - try { - const requestCount = parseInt(req.query.count); - const dbRes = feedDBInst.selectItems(requestCount); - if (dbRes.success) return res.status(200).json(dbRes.data); - else return res.status(500).json({ error: dbRes.data }) - } catch (e) { - return res.status(500).json({ error: e }); - } +router.get("/getFeed", (req, res) => { + try { + const requestCount = parseInt(req.query.count); + const dbRes = feedDBInst.selectItems(requestCount); + if (dbRes.success) return res.status(200).json(dbRes.data); + else return res.status(500).json({ error: dbRes.data }); + } catch (e) { + return res.status(500).json({ error: e }); + } +}); + +router.post("/addFeed", (req, res) => { + try { + const { title, content, type } = req.body; + const addResult = feedDBInst.insertItem({ title, content, type }); + if (!addResult) return res.status(500).json({ error: dbRes.data }); + else return res.status(200).json({ isOK: true }); + } catch (e) { + return res.status(500).json({ error: e }); + } }); -router.post('/addFeed', (req, res) => { - try { - const { title, content } = req.body; - const addResult = feedDBInst.insertItem({ title, content }); - if (!addResult) return res.status(500).json({ error: dbRes.data }) - else return res.status(200).json({ isOK: true }); - } catch (e) { - return res.status(500).json({ error: e }); - } +router.post("/deleteFeed", (req, res) => { + try { + const { id } = req.body; + const deleteResult = feedDBInst.deleteItem(parseInt(id)); + if (!deleteResult) + return res.status(500).json({ error: "No item deleted" }); + else return res.status(200).json({ isOK: true }); + } catch (e) { + return res.status(500).json({ error: e }); + } }); -router.post('/deleteFeed', (req, res) => { - try { - const { id } = req.body; - const deleteResult = feedDBInst.deleteItem(parseInt(id)); - if (!deleteResult) return res.status(500).json({ error: "No item deleted" }) - else return res.status(200).json({ isOK: true }); - } catch (e) { - return res.status(500).json({ error: e }); - } -}) +router.post("/editFeed", (req, res) => { + try { + const { id, title, content, type } = req.body; + feedDBInst.editItem(parseInt(id), title, content, type); + return res.status(200).json({ isOK: true }); + } catch (e) { + return res.status(500).json({ error: e }); + } +}); -module.exports = router; \ No newline at end of file +module.exports = router; From 3d5e1cb97a67e747a985d0235f08f68caff79c09 Mon Sep 17 00:00:00 2001 From: malloc Date: Tue, 9 Apr 2024 23:42:48 +0900 Subject: [PATCH 3/3] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a8a60a..ec930cc 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ Repository for Node / Express Seminar @ SPARCS Code by [Jiho Park](https://github.com/UrWrstNightmare) (Night @ SPARCS) +Task Done by [Jeeyun Choi]() (malloc @ SPARCS) --- ### Folder Structure |- client : Example client for seminar\ -|- seminar: Files used in seminar\ -|- homework : Homework Directory +|- seminar: Files used in seminar ### How to Run +Do `npm install` and `npm start` on both client & seminar directory \ No newline at end of file