frontend api refactoring, req.user handling on backend
This commit is contained in:
27
client/src/components/derived/CollectionList.tsx
Normal file
27
client/src/components/derived/CollectionList.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect } from "react";
|
||||
import { useAuthContext } from "../../context/AuthContext"
|
||||
import API from "../../util/API";
|
||||
import { Card } from "../ui"
|
||||
|
||||
function CollectionList() {
|
||||
const { user, token } = useAuthContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (user && token) {
|
||||
(async() => {
|
||||
const Collections = new API.Collection(token);
|
||||
const result = await Collections.getAllAuthored();
|
||||
console.log(result);
|
||||
})();
|
||||
}
|
||||
}, [user])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
||||
</Card>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default CollectionList
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import { v4 } from "uuid";
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import { getAllUsers, getFriendships, getPendingFriendRequests, getUserByID } from "../../util/apiUtils";
|
||||
import API from "../../util/API";
|
||||
import UserCard from "../ui/UserCard";
|
||||
import { IUser, IFriendship } from "../../schemas";
|
||||
import { Card, Divider, Panel } from "../ui";
|
||||
@@ -10,27 +11,32 @@ import FriendSearchWidget from "../ui/Widgets/FriendSearchWidget";
|
||||
export default function Friends() {
|
||||
const [friends, setFriends] = useState<IFriendship[]>();
|
||||
const [userList, setUserList] = useState(new Array<IUser>());
|
||||
const { user } = useAuthContext();
|
||||
const { user, token } = useAuthContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
if (!user || !token) return;
|
||||
(async function() {
|
||||
try {
|
||||
const rawResult = await getFriendships();
|
||||
const Friends = new API.Friendship(token);
|
||||
const result = await Friends.getAll();
|
||||
|
||||
if (rawResult.length) {
|
||||
const result = rawResult.filter((item: IFriendship) => (item.senderid == user.id) && !(item.pending));
|
||||
if (result.length) {
|
||||
setFriends(result);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
|
||||
console.log(result);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})()
|
||||
}, [user])
|
||||
})();
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
friends && friends.map(async (friend: IFriendship) => {
|
||||
const userData = await getUserByID(friend.targetid);
|
||||
if (!token || !friends) return;
|
||||
|
||||
friends.map(async (friend: IFriendship) => {
|
||||
const Friends = new API.Friendship(token);
|
||||
const userData = await Friends.getByID(friend.targetid as string);
|
||||
if (userData) setUserList((prev: IUser[]) => {
|
||||
return [...prev, userData]
|
||||
})
|
||||
@@ -45,14 +51,14 @@ export default function Friends() {
|
||||
<>
|
||||
{ userList.length ?
|
||||
(
|
||||
<Panel extraStyles="flex-row">
|
||||
<Card extraStyles="flex-row">
|
||||
<h2>Your friendships:</h2>
|
||||
{
|
||||
userList.map((user: IUser) => {
|
||||
return <UserCard key={v4()} user={user} />
|
||||
})
|
||||
}
|
||||
</Panel>
|
||||
</Card>
|
||||
) :
|
||||
(
|
||||
<Card>
|
||||
|
||||
@@ -1,22 +1,118 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IUser } from "../../schemas";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthContext, useAuthContext } from "../../context/AuthContext";
|
||||
import { Button, Page } from "../ui";
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import { Button, Divider, Page, Panel } from "../ui";
|
||||
import Protect from "../../util/Protect";
|
||||
import Friends from "../derived/Friends";
|
||||
import API from "../../util/API";
|
||||
import { AxiosError } from "axios";
|
||||
import CollectionList from "../derived/CollectionList";
|
||||
|
||||
export default function Profile() {
|
||||
const [message, setMessage] = useState<JSX.Element>();
|
||||
const { user } = useAuthContext();
|
||||
const [contents, setContents] = useState<JSX.Element>(<></>);
|
||||
const [targetUser, setTargetUser] = useState<IUser>();
|
||||
const { user, token } = useAuthContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Protect redirect="profile">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{user && user.firstname}'s Profile</h1>
|
||||
<Friends />
|
||||
</div>
|
||||
</Protect>
|
||||
)
|
||||
const dateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: "long" });
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const targetID = params.get('id');
|
||||
|
||||
if (targetID) {
|
||||
(async() => {
|
||||
try {
|
||||
const User = new API.User(token);
|
||||
const result = await User.getByID(targetID);
|
||||
if (result) setTargetUser(result);
|
||||
} catch (error) {
|
||||
if (error instanceof AxiosError) {
|
||||
if (error?.response?.status == 404) {
|
||||
// to do: replace with customizable 404 page
|
||||
setContents(
|
||||
<Page>
|
||||
<h1>404: Not found</h1>
|
||||
<Divider />
|
||||
<Panel>
|
||||
<p>No user found with ID {targetID}</p>
|
||||
<Button onClick={() => navigate('/')}>Home</Button>
|
||||
</Panel>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
const formattedDate = user!.datecreated ? dateFormatter.format(new Date(user!.datecreated)) : "(unknown)";
|
||||
|
||||
setContents(
|
||||
<Protect redirect="profile">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{user!.firstname}'s Profile</h1>
|
||||
|
||||
<div className="profile-grid">
|
||||
<Panel>
|
||||
<h2>About me:</h2>
|
||||
<p>{user!.firstname} {user!.lastname}</p>
|
||||
<p>Recipin Member since: {formattedDate}</p>
|
||||
<Divider />
|
||||
<p>30 recipes</p>
|
||||
<p>2 collections</p>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
{/* include number of collections */}
|
||||
<h2>My collections:</h2>
|
||||
<CollectionList />
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My friends:</h2>
|
||||
<Friends />
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</Protect>
|
||||
)
|
||||
}
|
||||
}, [token])
|
||||
|
||||
useEffect(() => {
|
||||
if (targetUser) {
|
||||
const formattedDate = targetUser.datecreated ? dateFormatter.format(new Date(targetUser.datecreated)) : "(unknown)";
|
||||
|
||||
setContents(
|
||||
<Protect redirect="/">
|
||||
<div className="profile-authenticated">
|
||||
<h1>{targetUser.firstname}'s Profile</h1>
|
||||
|
||||
<div className="profile-grid">
|
||||
<Panel>
|
||||
<h2>About {targetUser.firstname} {targetUser.lastname}:</h2>
|
||||
<p>Recipin Member since: {formattedDate}</p>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My collections:</h2>
|
||||
</Panel>
|
||||
|
||||
<Panel>
|
||||
<h2>My friends:</h2>
|
||||
<Friends />
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</Protect>
|
||||
)
|
||||
}
|
||||
}, [targetUser]);
|
||||
|
||||
return contents
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { v4 } from "uuid";
|
||||
import { RegisterVariantType, VariantLabel } from ".";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
import { IUser, IUserAuth } from "../../../schemas";
|
||||
import { attemptLogin, attemptRegister } from "../../../util/apiUtils";
|
||||
import { IUser } from "../../../schemas";
|
||||
import API from "../../../util/API";
|
||||
import { Button, Page, Panel } from "../../ui";
|
||||
import Divider from "../../ui/Divider";
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { RegisterVariantType, VariantLabel } from ".";
|
||||
import { useNow } from "../../../hooks/useNow";
|
||||
import { ICollection, IUser, IUserAuth } from "../../../schemas";
|
||||
import { attemptLogin, createNewCollection } from "../../../util/apiUtils";
|
||||
import { ICollection } from "../../../schemas";
|
||||
import API from "../../../util/API";
|
||||
import { Button, Divider, Page, Panel } from "../../ui";
|
||||
import TextField from "../../ui/TextField";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
|
||||
const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) => {
|
||||
@@ -28,10 +26,7 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) =>
|
||||
datemodified: now
|
||||
}
|
||||
|
||||
console.log(collection);
|
||||
|
||||
const result = await collectionAPI.post(collection);
|
||||
console.log(result);
|
||||
if (result) transitionDisplay(VariantLabel.AddFriends);
|
||||
}
|
||||
|
||||
@@ -50,7 +45,7 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) =>
|
||||
<h3>What would you like to call your main collection?</h3>
|
||||
|
||||
{/* <TextField onChange={(e: ChangeEvent<HTMLInputElement>) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'} /> */}
|
||||
<input type="text" onChange={(e) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'}></input>
|
||||
<input type="text" onChange={(e) => setCollectionName(e.target.value)} placeholder={user.firstname + '\'s Collection'}></input>
|
||||
</Panel>
|
||||
|
||||
<Button onClick={handleClick}>Next</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { FC, useState } from "react";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
import { IUser } from "../../../schemas";
|
||||
import AboutYou from "./aboutyou";
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuthContext } from "../../context/AuthContext";
|
||||
import { attemptLogout, checkCredientials } from "../../util/apiUtils";
|
||||
import { Button, Page, Panel } from "../ui"
|
||||
import Divider from "../ui/Divider";
|
||||
|
||||
const Welcome = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user, setUser } = useAuthContext();
|
||||
const { user } = useAuthContext();
|
||||
|
||||
const authUserActions = (
|
||||
<Panel extraStyles="inherit-background c-papyrus uppercase flexrow">
|
||||
@@ -21,7 +19,6 @@ const Welcome = () => {
|
||||
<Panel extraStyles="inherit-background c-papyrus uppercase">
|
||||
<h2>Ready to get started?</h2>
|
||||
<Button onClick={() => navigate('/register')}>Register</Button>
|
||||
<Button onClick={attemptLogout}>Log Out</Button>
|
||||
</Panel>
|
||||
)
|
||||
|
||||
@@ -41,7 +38,6 @@ const Welcome = () => {
|
||||
|
||||
<Panel extraStyles="inherit-background c-papyrus uppercase">
|
||||
<h2>Build Shopping Lists Directly from Your Recipes</h2>
|
||||
<button onClick={checkCredientials}></button>
|
||||
</Panel>
|
||||
|
||||
<Divider />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import API from "../../../util/API";
|
||||
import { NavbarType } from "../../../util/types";
|
||||
import { Button, Dropdown } from '..'
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useAuthContext } from "../../../context/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@@ -14,8 +13,7 @@ const LoggedIn = () => {
|
||||
const [searchActive, setSearchActive] = useState(false);
|
||||
|
||||
const handleLogout = async () => {
|
||||
const success = await auth.logout();
|
||||
console.log(success);
|
||||
await auth.logout();
|
||||
|
||||
// nullify cookie and unset user/token data
|
||||
document.cookie = `token=;expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||
@@ -40,10 +38,6 @@ const LoggedIn = () => {
|
||||
navigate(payload);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(user);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id="navbar">
|
||||
|
||||
@@ -3,6 +3,13 @@ import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroc
|
||||
import { default as _instance } from "./axiosInstance";
|
||||
|
||||
module API {
|
||||
export enum CRUDMETHOD {
|
||||
GET,
|
||||
PUT,
|
||||
POST,
|
||||
DELETE
|
||||
}
|
||||
|
||||
export class Settings {
|
||||
private static APISTRING = import.meta.env.APISTRING || "http://localhost:8080";
|
||||
private static token?: string;
|
||||
@@ -23,7 +30,7 @@ module API {
|
||||
abstract class RestController<T> {
|
||||
protected instance = _instance;
|
||||
protected endpoint: string;
|
||||
protected headers?: any
|
||||
protected headers?: any;
|
||||
|
||||
constructor(endpoint: string, token: string) {
|
||||
this.endpoint = endpoint;
|
||||
@@ -35,6 +42,19 @@ module API {
|
||||
};
|
||||
}
|
||||
|
||||
async customRoute(method: CRUDMETHOD, path: string, data?: any, requireHeaders = true) {
|
||||
switch (method) {
|
||||
case CRUDMETHOD.GET:
|
||||
return this.instance.get(this.endpoint + path, (requireHeaders && this.headers));
|
||||
case CRUDMETHOD.PUT:
|
||||
return this.instance.put(this.endpoint + path, data, (requireHeaders && this.headers));
|
||||
case CRUDMETHOD.POST:
|
||||
return this.instance.post(this.endpoint + path, data, (requireHeaders && this.headers));
|
||||
case CRUDMETHOD.DELETE:
|
||||
return this.instance.delete(this.endpoint + path, (requireHeaders && this.headers));
|
||||
}
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
const response = await this.instance.get(this.endpoint, this.headers);
|
||||
return Promise.resolve(response.data);
|
||||
@@ -144,6 +164,11 @@ module API {
|
||||
constructor(token: string) {
|
||||
super(Settings.getAPISTRING() + "/app/collection", token);
|
||||
}
|
||||
|
||||
async getAllAuthored() {
|
||||
const response = await this.customRoute(CRUDMETHOD.GET, "?authored=true");
|
||||
return Promise.resolve(response.data);
|
||||
}
|
||||
}
|
||||
|
||||
export class GroceryList extends RestController<IGroceryList> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IUser } from "../schemas";
|
||||
dotenv.config();
|
||||
|
||||
export function restrictAccess(req: Request, res: Response, next: NextFunction) {
|
||||
if (req.session.user == undefined) {
|
||||
if (req.user == undefined) {
|
||||
res.send("content restricted");
|
||||
} else {
|
||||
next();
|
||||
|
||||
@@ -23,6 +23,13 @@ export default class CollectionCtl {
|
||||
return new ControllerResponse(code, data);
|
||||
}
|
||||
|
||||
async getAllAuthored(id: number | string) {
|
||||
const result = await CollectionInstance.getAllAuthored(id);
|
||||
const code = (result !== null) ? StatusCode.OK : StatusCode.NotFound;
|
||||
const data = result || "No collections found";
|
||||
return new ControllerResponse(code, data);
|
||||
}
|
||||
|
||||
async getUserDefault(id: number | string) {
|
||||
const result = await CollectionInstance.getUserDefault(id);
|
||||
const code = (result !== null) ? StatusCode.OK : StatusCode.NotFound;
|
||||
|
||||
@@ -18,6 +18,22 @@ export class Collection {
|
||||
}
|
||||
}
|
||||
|
||||
async getAllAuthored(id: number | string) {
|
||||
console.log(id, typeof id);
|
||||
try {
|
||||
const statement = `
|
||||
SELECT * FROM recipin.collection
|
||||
WHERE ownerid = $1;
|
||||
`
|
||||
const result = await pool.query(statement, [id]);
|
||||
console.log(result.rows);
|
||||
if (result.rows.length) return result.rows;
|
||||
return null;
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async getUserDefault(id: number | string) {
|
||||
try {
|
||||
const statement = `
|
||||
@@ -46,7 +62,6 @@ export class Collection {
|
||||
}
|
||||
|
||||
async post(data: ICollection) {
|
||||
console.log('new default collection');
|
||||
const { name, active, ismaincollection, ownerid } = data;
|
||||
try {
|
||||
const statement = `
|
||||
|
||||
@@ -16,11 +16,6 @@ const router = Router();
|
||||
export const authRoute = (app: Express) => {
|
||||
app.use('/auth', router);
|
||||
|
||||
router.use((req, res, next) => {
|
||||
console.log(req.session);
|
||||
next();
|
||||
})
|
||||
|
||||
router.get('/', restrictAccess, (req, res, next) => {
|
||||
if (req.session.user) {
|
||||
const user = req.session.user;
|
||||
@@ -44,8 +39,6 @@ export const authRoute = (app: Express) => {
|
||||
router.post('/login', async (req, res, next) => {
|
||||
try {
|
||||
const data: IUserAuth = req.body;
|
||||
console.log(data);
|
||||
|
||||
const response: ControllerResponse<any> = await AuthInstance.login(data);
|
||||
|
||||
if (response.ok) {
|
||||
@@ -70,8 +63,6 @@ export const authRoute = (app: Express) => {
|
||||
return next(err);
|
||||
})
|
||||
|
||||
console.log(req.session);
|
||||
|
||||
res.cookie('token', token, { httpOnly: true });
|
||||
res.json({ token });
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Express, Router } from "express";
|
||||
import { checkIsAdmin, restrictAccess } from "../auth/middlewares";
|
||||
import CollectionCtl from "../controllers/CollectionCtl";
|
||||
import { IUser } from "../schemas";
|
||||
const CollectionInstance = new CollectionCtl();
|
||||
|
||||
const router = Router();
|
||||
@@ -8,12 +9,6 @@ const router = Router();
|
||||
export const collectionRoute = (app: Express) => {
|
||||
app.use('/app/collection', router);
|
||||
|
||||
router.use((req, res, next) => {
|
||||
console.log('what gives');
|
||||
console.log(req.body);
|
||||
next();
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
@@ -24,11 +19,32 @@ export const collectionRoute = (app: Express) => {
|
||||
}
|
||||
})
|
||||
|
||||
// implement is admin on this route
|
||||
router.get('/', checkIsAdmin, async (req, res, next) => {
|
||||
router.get('&authored=true', restrictAccess, async (req, res, next) => {
|
||||
const user = req.user as IUser;
|
||||
console.log(user.id);
|
||||
try {
|
||||
const { code, data } = await CollectionInstance.getAll();
|
||||
const { code, data } = await CollectionInstance.getAllAuthored(user.id as number);
|
||||
res.status(code).send(data);
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
})
|
||||
|
||||
// implement is admin on this route
|
||||
router.get('/', restrictAccess, async (req, res, next) => {
|
||||
const user = req.user as IUser;
|
||||
const { authored } = req.query;
|
||||
|
||||
try {
|
||||
if (authored || authored == "true") {
|
||||
const { code, data } = await CollectionInstance.getAllAuthored(user.id as number);
|
||||
res.status(code).send(data);
|
||||
} else {
|
||||
if (user.isadmin) {
|
||||
const { code, data } = await CollectionInstance.getAll();
|
||||
res.status(code).send(data);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
next(e);
|
||||
}
|
||||
@@ -36,7 +52,6 @@ export const collectionRoute = (app: Express) => {
|
||||
|
||||
router.post('/', async (req, res, next) => {
|
||||
const data = req.body;
|
||||
console.log(req.body ?? "sanity check");
|
||||
|
||||
try {
|
||||
const result = await CollectionInstance.post(data);
|
||||
|
||||
@@ -21,9 +21,7 @@ export const routes = async (app: Express) => {
|
||||
// middleware to check for auth on cookies on each request in protected routes
|
||||
app.use('/app', async (req, res, next) => {
|
||||
// pull jwt from request headers
|
||||
console.log(req.headers);
|
||||
const token = req.headers['authorization']?.split(" ")[1];
|
||||
console.log(token);
|
||||
|
||||
if (!token) {
|
||||
res.status(403).send("Unauthorized, did not receive token");
|
||||
@@ -32,8 +30,8 @@ export const routes = async (app: Express) => {
|
||||
if (err) {
|
||||
res.status(403).send(err);
|
||||
} else {
|
||||
console.log(data);
|
||||
req.user = data;
|
||||
// @ts-ignore
|
||||
req.user = data.user;
|
||||
next();
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user