Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Repository for Node / Express Seminar @ SPARCS

Code by [Jiho Park](https://github.com/UrWrstNightmare) (Night @ SPARCS)
Finished by [Sé Bang](https://github.com/sciberbee) (cyber @ SPARCS)

---
### Folder Structure
Expand All @@ -11,3 +12,8 @@ Code by [Jiho Park](https://github.com/UrWrstNightmare) (Night @ SPARCS)


### How to Run
change to example2 branch
and run 2 commands in both 'seminar' and 'client' directories:

npm install
npm start
3 changes: 3 additions & 0 deletions client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>SPARCS Backend Seminar Demo Front End</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Varela+Round&display=swap" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
4 changes: 2 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import PageNotFound from "./pages/404";
import Footer from "./components/footer";
import './App.css';
import AccountPage from "./pages/account";
import CatImagePage from "./pages/cat-image";
import KawaiiStagram from "./pages/kawaii-stagram";


const App = () => {
Expand All @@ -18,7 +18,7 @@ const App = () => {
<Route path="/" element={ <HomePage/> }/>
<Route path="/feed" element={ <FeedPage/> }/>
<Route path="/account" element={ <AccountPage/> }/>
<Route path="/cat-image" element={ <CatImagePage/> }/>
<Route path="/kawaii-stagram" element={ <KawaiiStagram/> }/>
<Route path="/ssr" element={ <SSRPage/> }/>
<Route path="*" element={ <PageNotFound/> }/>
</Routes>
Expand Down
16 changes: 10 additions & 6 deletions client/src/pages/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import {SAPIBase} from "../tools/api";
import "./css/account.css";

const AccountPage = () => {
const [ SAPIKEY, setSAPIKEY ] = React.useState<string>("");
const [ SAPIUSERID, setSAPIUSERID ] = React.useState<string>(""); // User ID
const [ SAPIPASSWORD, setSAPIPASSWORD ] = React.useState<string>(""); // Password
const [ NBalance, setNBalance ] = React.useState<number | "Not Authorized">("Not Authorized");
const [ NTransaction, setNTransaction ] = React.useState<number | ''>(0);

const getAccountInformation = () => {
const asyncFun = async() => {
interface IAPIResponse { balance: number };
const { data } = await axios.post<IAPIResponse>(SAPIBase + '/account/getInfo', { credential: SAPIKEY });
const { data } = await axios.post<IAPIResponse>(SAPIBase + '/account/getInfo', { userId: SAPIUSERID, password: SAPIPASSWORD });
setNBalance(data.balance);
}
asyncFun().catch((e) => window.alert(`AN ERROR OCCURED: ${e}`));
Expand All @@ -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<IAPIResponse>(SAPIBase + '/account/transaction', { credential: SAPIKEY, amount: amount });
const { data } = await axios.post<IAPIResponse>(SAPIBase + '/account/transaction', { userId: SAPIUSERID, password: SAPIPASSWORD, amount: amount });
setNTransaction(0);
if (!data.success) {
window.alert('Transaction Failed:' + data.msg);
Expand All @@ -39,10 +40,13 @@ const AccountPage = () => {
<div className={"account"}>
<Header/>
<h2>Account</h2>
<div className={"account-token-input"}>
Enter API Key: <input type={"text"} value={SAPIKEY} onChange={e => setSAPIKEY(e.target.value)}/>
<button onClick={e => getAccountInformation()}>GET</button>
<div className={"account-username-input"}>
Enter User ID: <input type={"text"} value={SAPIUSERID} onChange={e => setSAPIUSERID(e.target.value)}/>
</div>
<div className={"account-password-input"}>
Enter Password: <input type={"password"} value={SAPIPASSWORD} onChange={e => setSAPIPASSWORD(e.target.value)}/>
</div>
<button onClick={e => getAccountInformation()}>GET</button>
<div className={"account-bank"}>
<h3>The National Bank of SPARCS API</h3>
<div className={"balance"}>
Expand Down
16 changes: 0 additions & 16 deletions client/src/pages/cat-image.tsx

This file was deleted.

14 changes: 14 additions & 0 deletions client/src/pages/css/feed.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,18 @@
.delete-item:hover {
font-weight: bold;
cursor: pointer;
}

.edit-item {
position: absolute;
right: 50px;
top: 12px;
color: blue;
font-size: 18px;
}

.edit-item:hover {
font-weight: bold;
cursor: pointer;
font-size: 24px;
}
64 changes: 64 additions & 0 deletions client/src/pages/css/kawaii-stagram.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.feed-list {
padding: 25px 10px;
width: calc(100% - 20px);
max-width: 800px;
margin: auto;
}

.feed-item {
border: solid 1px rgba(0,0,0,0.2);
border-radius: 15px;
width: 100%;
margin: 10px 0;
position: relative;
background-color: #FFD700; /* Golden yellow */
box-shadow: 0 0 10px rgba(0,0,0,0.2);
font-family: "Varela Round", sans-serif;
font-size: larger;
font-weight: 400;
font-style: normal;
}

.feed-item-add {
border: dotted 1px black;
border-radius: 15px;
padding: 20px 10px 0 10px;
}

.post-add-button {
margin-top: 20px;
padding: 10px;
border-top: solid 1px rgba(0,0,0,0.1);
}

.post-add-button:hover {
cursor: pointer;
font-weight: 700;
}

.delete-item {
position: absolute;
right: 15px;
top: 12px;
color: red;
font-size: 18px;
}

.delete-item:hover {
font-weight: bold;
cursor: pointer;
}

.edit-item {
position: absolute;
right: 50px;
top: 12px;
color: blue;
font-size: 18px;
}

.edit-item:hover {
font-weight: bold;
cursor: pointer;
font-size: 24px;
}
20 changes: 20 additions & 0 deletions client/src/pages/feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ const FeedPage = (props: {}) => {
asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`));
}

const editPost = (id: string, newTitle: string, newContent: string) => {
const asyncFun = async () => {
await axios.post( SAPIBase + '/feed/editFeed', { id: id, title: newTitle, content: newContent } );
const updatedPosts = LAPIResponse.map( (val) => {
if (val.id === parseInt(id)) {
val.title = newTitle;
val.content = newContent;
}
return val;
});
setLAPIResponse(updatedPosts);
}
asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`));
}

return (
<div className="Feed">
<Header/>
Expand All @@ -58,6 +73,11 @@ const FeedPage = (props: {}) => {
{ LAPIResponse.map( (val, i) =>
<div key={i} className={"feed-item"}>
<div className={"delete-item"} onClick={(e) => deletePost(`${val.id}`)}>ⓧ</div>
<div className={"edit-item"} onClick={(e) => {
const newTitle = window.prompt("Enter the new title", val.title);
const newContent = window.prompt("Enter the new content", val.content);
if (newTitle && newContent) editPost(`${val.id}`, newTitle, newContent);
}}>🖍️</div>
<h3 className={"feed-title"}>{ val.title }</h3>
<p className={"feed-body"}>{ val.content }</p>
</div>
Expand Down
10 changes: 3 additions & 7 deletions client/src/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,9 @@ const HomePage = (props: {}) => {
<div className={"page-subtitle"}>Example #2</div>
<div className={"page-title"}>Middleware & Authorization</div>
</div>
<div className={"page-link"} onClick={ () => navigate("/cat-image") }>
<div className={"page-subtitle"}>Example #3</div>
<div className={"page-title"}>Serve *Cute* Image Files</div>
</div>
<div className={"page-link"} onClick={ () => navigate("/ssr") }>
<div className={"page-subtitle"}>Example #4</div>
<div className={"page-title"}>Server Side Rendering</div>
<div className={"page-link"} onClick={ () => navigate("/kawaii-stagram") }>
<div className={"page-subtitle"}>cyber's example</div>
<div className={"page-title"}>Kawaii-stagram</div>
</div>
</div>
</div>
Expand Down
102 changes: 102 additions & 0 deletions client/src/pages/kawaii-stagram.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from "react";
import axios from "axios";
import { SAPIBase } from "../tools/api";
import Header from "../components/header";
import "./css/kawaii-stagram.css";

interface IAPIResponse { id: number, title: string, description: string, image: string }

const KawaiiStagram = () => {
const [ LAPIResponse, setLAPIResponse ] = React.useState<IAPIResponse[]>([]);
const [ NPostCount, setNPostCount ] = React.useState<number>(0);
const [ SNewPostTitle, setSNewPostTitle ] = React.useState<string>("");
const [ SNewPostdescription, setSNewPostdescription ] = React.useState<string>("");
const [ SNewPostImage, setSNewPostImage ] = React.useState<string>("");

React.useEffect( () => {
let BComponentExited = false;
const asyncFun = async () => {
const { data } = await axios.get<IAPIResponse[]>( SAPIBase + `/kawaii-stagram/getFeed?count=${ NPostCount }`);
console.log(data);
if (BComponentExited) return;
setLAPIResponse(data);
};
asyncFun().catch((e) => window.alert(`Error while running API Call: ${e}`));
return () => { BComponentExited = true; }
}, [ NPostCount ]);

const createNewPost = () => {
const asyncFun = async () => {
await axios.post( SAPIBase + '/kawaii-stagram/addFeed', { title: SNewPostTitle, description: SNewPostdescription, image: SNewPostImage } );
setNPostCount(NPostCount + 1);
setSNewPostTitle("");
setSNewPostdescription("");
setSNewPostImage("");
}
asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`));
}

const deletePost = (id: string) => {
const asyncFun = async () => {
// One can set X-HTTP-Method header to DELETE to specify deletion as well
await axios.post( SAPIBase + '/kawaii-stagram/deleteFeed', { id: id } );
setNPostCount(Math.max(NPostCount - 1, 0));
}
asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`));
}

const editPost = (id: string, newTitle: string, newDescription: string, newImage: string) => {
const asyncFun = async () => {
await axios.post( SAPIBase + '/kawaii-stagram/editFeed', { id: id, title: newTitle, description: newDescription, image: newImage } );
const updatedPosts = LAPIResponse.map( (val) => {
if (val.id === parseInt(id)) {
val.title = newTitle;
val.description = newDescription;
val.image = newImage;
}
return val;
});
setLAPIResponse(updatedPosts);
}
asyncFun().catch(e => window.alert(`AN ERROR OCCURED! ${e}`));
}

return (
<div className="Feed">
<Header/>
<h2>Kawaii-Stagram</h2>
<div className={"feed-length-input"}>
Number of posts to show: &nbsp;&nbsp;
<input type={"number"} value={ NPostCount } id={"post-count-input"} min={0}
onChange={ (e) => setNPostCount( parseInt(e.target.value) ) }
/>
</div>
<div className={"feed-list"}>
{ LAPIResponse.map( (val, i) =>
<div key={i} className={"feed-item"}>
<div className={"delete-item"} onClick={() => deletePost(`${val.id}`)}>ⓧ</div>
<div className={"edit-item"} onClick={() => {
const newTitle = window.prompt("Enter the new title", val.title);
const newdescription = window.prompt("Enter the new description", val.description);
const newImage = window.prompt("Enter the link of image", val.image);
if (newTitle && newdescription && newImage) editPost(`${val.id}`, newTitle, newdescription, newImage);
}}>🖍️</div>
<h3 className={"feed-title"}>{ val.title }</h3>
<p className={"feed-body"}>{ val.description }</p>
<img className={"feed-image"} src={ val.image } alt={ val.title }/>
</div>
) }
<div className={"feed-item-add"}>
Title: <input type={"text"} value={SNewPostTitle} onChange={(e) => setSNewPostTitle(e.target.value)}/>
&nbsp;&nbsp;&nbsp;&nbsp;
description: <input type={"text"} value={SNewPostdescription} onChange={(e) => setSNewPostdescription(e.target.value)}/>
&nbsp;&nbsp;&nbsp;&nbsp;
Image: <input type={"text"} value={SNewPostImage} onChange={(e) => setSNewPostImage(e.target.value)}/>
<div className={"post-add-button"} onClick={() => createNewPost()}>Add Post!</div>
</div>
</div>
</div>
);
}

export default KawaiiStagram;
4 changes: 3 additions & 1 deletion seminar/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
PORT=8080
NODE_ENV=DEVELOPMENT
API_KEY=
API_KEY=
API_USERID=sciberbee
API_PASSWORD=331
2 changes: 2 additions & 0 deletions seminar/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require('dotenv').config();
const statusRouter = require('./routes/status');
const feedRouter = require('./routes/feed');
const accountRouter = require('./routes/account');
const kawaiiStagramRouter = require('./routes/kawaii-stagram');

const app = express();
const port = process.env.PORT;
Expand All @@ -26,6 +27,7 @@ app.use(cors(corsOptions));
app.use('/status', statusRouter);
app.use('/feed', feedRouter);
app.use('/account', accountRouter);
app.use('/kawaii-stagram', kawaiiStagramRouter);

app.listen(port, () => {
console.log(`Example App Listening @ http://localhost:${ port }`);
Expand Down
3 changes: 2 additions & 1 deletion seminar/src/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const authMiddleware = (req, res, next) => {
if (req.body.credential === process.env.API_KEY) {
const { userId, password } = req.body; // Destructure user ID and password from the request body
if (userId === process.env.API_USERID && password === process.env.API_PASSWORD) { // Check if the user ID and password match the expected values
console.log("[AUTH-MIDDLEWARE] Authorized User");
next();
}
Expand Down
Loading