diff --git a/client/package.json b/client/package.json
index 6b97e4f..148c6da 100644
--- a/client/package.json
+++ b/client/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@tinymce/tinymce-react": "^4.2.0",
"axios": "^1.2.0",
+ "jwt-decode": "^3.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.3",
@@ -18,6 +19,7 @@
"uuid": "^9.0.0"
},
"devDependencies": {
+ "@types/jwt-decode": "^3.1.0",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@types/uuid": "^8.3.4",
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 5e7c3a7..653e17f 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,11 +1,11 @@
// framework tools and custom utils
-import { useCallback, useContext, useEffect, useState } from 'react';
+import { useEffect } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
-import { AuthContext, IAuthContext, useAuthContext } from './context/AuthContext';
-import { attemptLogout, checkCredientials } from './util/apiUtils';
-import { IUser } from './schemas';
+import { useAuthContext } from './context/AuthContext';
+import jwtDecode from 'jwt-decode';
+import API from './util/API';
-// pages, ui, styles
+// pages, ui, components, styles
import Subscriptions from './components/pages/Subscriptions/Subscriptions';
import Browser from './components/ui/Browser';
import Collection from './components/pages/Collection';
@@ -19,55 +19,51 @@ import CollectionBrowser from './components/pages/CollectionBrowser';
import { Navbar } from './components/ui';
import GroceryList from './components/pages/GroceryList';
import GroceryListCollection from './components/pages/GroceryListCollection';
+import { TokenType } from './util/types';
import './sass/App.scss';
+import handleToken from './util/handleToken';
function App() {
- const [user, setUser] = useState();
- const parentState = { user, setUser };
-
- const receiveChange = (() => {});
+ const { setUser, token, setToken } = useAuthContext();
useEffect(() => {
- const wrapper = async () => {
- try {
- const result: IAuthContext | undefined = await checkCredientials();
-
- if (result == undefined) {
- setUser({ user: undefined });
- } else {
- setUser(result);
- }
- } catch(e) {
- console.error(e);
+ if (document.cookie) {
+ const response = handleToken();
+ if (response) {
+ setToken(response.token);
+ setUser(response.user);
}
}
+ }, [document.cookie]);
- wrapper();
- }, [])
+ useEffect(() => {
+ if (token) {
+ const response = handleToken();
+ response && setUser(response.user);
+ }
+ }, [setToken])
return (
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- {}} />} />
- } />
- } />
- } />
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ {}} />} />
+ } />
+ } />
+ } />
- } />
- } />
- } />
-
-
-
+ } />
+ } />
+ } />
+
+
)
}
diff --git a/client/src/components/pages/AddRecipe.tsx b/client/src/components/pages/AddRecipe.tsx
index 9cd599b..792e07e 100644
--- a/client/src/components/pages/AddRecipe.tsx
+++ b/client/src/components/pages/AddRecipe.tsx
@@ -10,7 +10,7 @@ const AddRecipe = () => {
const getFormState = useCallback((data: IRecipe) => {
setInput(data);
- }, [])
+ }, [input])
const handleCreate = () => {
for (let field of Object.keys(input)) {
@@ -28,20 +28,6 @@ const AddRecipe = () => {
}
})
}, [authContext])
-
- useEffect(() => {
- input.authoruserid && setForm(
- new Form({
- parent: "AddRecipe",
- keys: ["name", "preptime", "course", "cuisine", "ingredients", "description"],
- labels: ["Recipe Name:", "Prep Time:", "Course:", "Cuisine:", "Ingredients:", "Description:"],
- dataTypes: ['text', 'text', 'custom picker', 'custom picker', 'custom picker', 'TINYMCE'],
- initialState: input,
- getState: getFormState,
- richTextInitialValue: "Enter recipe details here!
"
- }).mount()
- )
- }, [input.authoruserid])
useEffect(() => {
console.log(input);
@@ -53,7 +39,18 @@ const AddRecipe = () => {
+
"
+ }} />
+
{ form || Loading...
}
+
diff --git a/client/src/components/pages/Login.tsx b/client/src/components/pages/Login.tsx
index 8fd6a74..4ad816a 100644
--- a/client/src/components/pages/Login.tsx
+++ b/client/src/components/pages/Login.tsx
@@ -1,46 +1,40 @@
import { useCallback, useContext, useEffect, useState } from "react";
-import { useNavigate, useParams } from "react-router-dom";
import { AuthContext, useAuthContext } from "../../context/AuthContext";
-import { attemptLogin } from "../../util/apiUtils";
-import { IUserAuth } from "../../schemas";
+import { useNavigate, useParams } from "react-router-dom";
+import { IUser, IUserAuth } from "../../schemas";
import { Button, Form, Page, Panel } from "../ui";
+import { FormConfig } from "../ui/Form";
+import API from "../../util/API";
export default function Login() {
const params = new URLSearchParams(window.location.search);
const redirect = params.get("redirect");
- const { user, setUser } = useContext(AuthContext);
+ const { user, setToken } = useContext(AuthContext);
// setup and local state
const navigate = useNavigate();
- const [form, setForm] = useState();
const [input, setInput] = useState({ email: '', password: '' });
// retrieve and store state from form
const getFormState = useCallback((received: IUserAuth) => {
setInput(received);
- }, [])
+ }, [input])
const handleLogin = async () => {
if (!input.email || !input.password) return;
- const { data, ok } = await attemptLogin(input);
- if (ok) setUser(data);
+ const result = await new API.Auth().login(input);
+
+ // setting token will trigger ui update
+ setToken(result.token);
+
+ // if there is a redirect, go there, else go home
navigate(`/${redirect ?? ''}`);
}
// check for logged in user and mount form
useEffect(() => {
if (user) navigate('/');
- setForm(
- new Form({
- parent: 'login',
- keys: Object.keys(input),
- labels: ["Email", "Password"],
- dataTypes: Object.keys(input),
- initialState: input,
- getState: getFormState
- }).mount()
- );
}, [])
return (
@@ -48,8 +42,18 @@ export default function Login() {
Hello! Nice to see you again.
- { form || Loading...
}
+
+
+
+
diff --git a/client/src/components/pages/Profile.tsx b/client/src/components/pages/Profile.tsx
index 73077a6..d319d30 100644
--- a/client/src/components/pages/Profile.tsx
+++ b/client/src/components/pages/Profile.tsx
@@ -14,8 +14,7 @@ export default function Profile() {
return (
-
{user?.firstname}'s Profile
-
Things and stuff!
+
{user && user.firstname}'s Profile
diff --git a/client/src/components/pages/Register/aboutyou.tsx b/client/src/components/pages/Register/aboutyou.tsx
index 3b6ad87..05865ce 100644
--- a/client/src/components/pages/Register/aboutyou.tsx
+++ b/client/src/components/pages/Register/aboutyou.tsx
@@ -5,9 +5,10 @@ import { RegisterVariantType, VariantLabel } from ".";
import { useAuthContext } from "../../../context/AuthContext";
import { IUser, IUserAuth } from "../../../schemas";
import { attemptLogin, attemptRegister } from "../../../util/apiUtils";
+import API from "../../../util/API";
import { Button, Page, Panel } from "../../ui";
import Divider from "../../ui/Divider";
-import Form, { FormConfig } from "../../ui/Form";
+import Form from "../../ui/Form";
const blankUser: IUser = {
firstname: '',
@@ -20,51 +21,31 @@ const blankUser: IUser = {
}
const AboutYou: RegisterVariantType = ({ transitionDisplay }) => {
+ const auth = new API.Auth();
const navigate = useNavigate();
- const authContext = useAuthContext();
- const [form, setForm] = useState(Loading content...
);
+ const { user, setToken } = useAuthContext();
const [input, setInput] = useState(blankUser);
- const [regSuccess, setRegSuccess] = useState();
const getFormState = useCallback((received: IUser) => {
setInput(received);
}, []);
useEffect(() => {
- if (authContext.user) navigate('/');
- }, [authContext]);
+ if (user) navigate('/');
+ }, [user]);
async function handleRegister() {
- const res = await attemptRegister(input);
+ const res = await auth.register(input);
if (res.ok) {
+ setTimeout(async () => {
+ const result = await auth.login(input);
+ setToken(result.token);
+ }, 750);
+
transitionDisplay(VariantLabel.InitialCollection, input);
}
}
- async function unwrapLogin() {
- const data: IUserAuth = { email: input.email, password: input.password || "" }
- const login = await attemptLogin(data);
- if (login) {
- authContext.user = login.user;
- }
- navigate('/');
- }
-
- useEffect(() => {
- setForm(new Form({
- parent: "register",
- keys: ['firstname', 'lastname', 'handle', 'email', 'password'],
- initialState: input,
- labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password"],
- dataTypes: ['text', 'text', 'text', 'email', 'password'],
- getState: getFormState
- }).mount());
- }, [])
-
- useEffect(() => {
- if (regSuccess) unwrapLogin();
- }, [regSuccess])
-
return (
Hi! Thanks for being here.
@@ -74,7 +55,16 @@ const AboutYou: RegisterVariantType = ({ transitionDisplay }) => {
Tell us a bit about yourself:
- { form || Loading...
}
+
+
+
diff --git a/client/src/components/pages/Register/collection.tsx b/client/src/components/pages/Register/collection.tsx
index ee97dfc..70ab9ea 100644
--- a/client/src/components/pages/Register/collection.tsx
+++ b/client/src/components/pages/Register/collection.tsx
@@ -3,33 +3,24 @@ import { RegisterVariantType, VariantLabel } from ".";
import { useNow } from "../../../hooks/useNow";
import { ICollection, IUser, IUserAuth } from "../../../schemas";
import { attemptLogin, createNewCollection } from "../../../util/apiUtils";
+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, receiveChange, input }) => {
+const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) => {
+ const { user, token } = useAuthContext();
const [collectionName, setCollectionName] = useState();
const [view, setView] = useState(Loading...
);
- const [user, setUser] = useState();
const now = useNow();
- async function unwrapLogin(data: IUser) {
- const userInfo: IUserAuth = { email: data.email, password: data.password! }
- const login = await attemptLogin(userInfo);
- setUser(login.user);
- }
-
- useEffect(() => {
- if (input) {
- setTimeout(() => {
- unwrapLogin(input);
- }, 750);
- }
- }, [])
-
const handleClick = async () => {
- if (!user) return;
+ if (!user || !token) return;
+
+ const collectionAPI = new API.Collection(token);
+
const collection: ICollection = {
- name: collectionName || (user.firstname + "'s Collection"),
+ name: collectionName ?? (user.firstname + "'s Collection"),
active: true,
ismaincollection: true,
ownerid: user.id!.toString(),
@@ -39,14 +30,13 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, receiveChan
console.log(collection);
- const result = await createNewCollection(collection);
+ const result = await collectionAPI.post(collection);
console.log(result);
if (result) transitionDisplay(VariantLabel.AddFriends);
}
useEffect(() => {
- if (user && receiveChange) {
- receiveChange(user);
+ if (user && token) {
setView(
Hi, {user.firstname}! Great to meet you.
@@ -59,7 +49,8 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, receiveChan
What would you like to call your main collection?
- ) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'} />
+ {/* ) => setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'} /> */}
+ setCollectionName(e.target.value)} placeholder={user.firstname + 's Collection'}>
diff --git a/client/src/components/pages/Register/index.tsx b/client/src/components/pages/Register/index.tsx
index 4d42422..bf39270 100644
--- a/client/src/components/pages/Register/index.tsx
+++ b/client/src/components/pages/Register/index.tsx
@@ -19,17 +19,17 @@ export enum VariantLabel {
FinishUp
}
-const Register: FC<{receiveChange: (change: IUser) => void}> = ({ receiveChange }) => {
+const Register = () => {
const [displayed, setDisplayed] = useState();
- const authContext = useAuthContext();
+ const { user } = useAuthContext();
- const transitionDisplay = (variant: number | VariantLabel, user?: IUser) => {
+ const transitionDisplay = (variant: number | VariantLabel) => {
switch (variant) {
case 0:
setDisplayed();
break;
case 1:
- setDisplayed();
+ setDisplayed();
break;
case 2:
setDisplayed();
@@ -38,7 +38,7 @@ const Register: FC<{receiveChange: (change: IUser) => void}> = ({ receiveChange
setDisplayed();
break;
default:
- setDisplayed();
+ setDisplayed();
break;
}
}
diff --git a/client/src/components/ui/Form.tsx b/client/src/components/ui/Form.tsx
index f0541a8..7704977 100644
--- a/client/src/components/ui/Form.tsx
+++ b/client/src/components/ui/Form.tsx
@@ -1,13 +1,6 @@
-import { ChangeEvent, FC } from "react";
-import { v4 } from 'uuid';
-import RichText from "./RichText";
-
-/**
- * For the generation of more complex form objects with
- * larger stateful values; expects to receive an object of
- * type T to a form which can mutate T with a state setter
- * of type Dispatch>
-**/
+import { ChangeEvent, FC, useEffect, useState } from "react"
+import { v4 } from "uuid"
+import RichText from "./RichText"
export interface FormConfig {
parent: string
@@ -20,83 +13,95 @@ export interface FormConfig {
extraStyles?: string
}
-export default class Form {
- private parent: string;
- private labels: string[];
- private keys: string[];
- private dataTypes: any[]
- private state: T;
- private getState: (received: T) => void
- private richTextInitialValue?: string;
- private extraStyles?: string
+interface FormProps {
+ parent: any
+ _config: FormConfig
+}
- constructor(config: FormConfig){
- this.parent = config.parent;
- this.keys = config.keys;
- this.labels = config.labels || this.keys;
- this.dataTypes = config.dataTypes || new Array(this.keys.length).fill('text');
- this.state = config.initialState;
- this.getState = config.getState;
- this.richTextInitialValue = config.richTextInitialValue;
- this.extraStyles = config.extraStyles;
+const Form: FC = ({ parent, _config }) => {
+ type T = typeof parent;
+ const { getState } = _config;
- this.mount();
- }
+ const [config, setConfig] = useState>();
+ const [state, setState] = useState();
+ const [contents, setContents] = useState();
- update(e: ChangeEvent, idx: number) {
- let newState = {
- ...this.state,
- [this.keys[idx]]: e.target['value' as keyof EventTarget]
- }
+ // initial setup
+ useEffect(() => {
+ if (!config) setConfig({
+ ..._config,
+ labels: _config.labels ?? _config.keys,
+ dataTypes: _config.dataTypes ?? new Array(_config.keys?.length).fill("text"),
+ });
- this.state = newState;
- this.getState(newState);
- }
+ if (!state) setState(_config.initialState);
+ }, [])
- updateRichText(txt: string, idx: number) {
- this.state = {
- ...this.state,
- [this.keys[idx]]: txt
- }
+ // usecallback handling
+ useEffect(() => {
+ state && getState(state);
+ }, [state]);
- this.getState(this.state);
- }
+ // update methods
+ function updateRichText(txt: string, idx: number) {
+ if (!config) return;
- mount() {
- let output = new Array();
-
- for (let i = 0; i < this.keys.length; i++) {
- let input: JSX.Element | null;
-
- if (this.dataTypes[i] == 'custom picker') {
- console.log('noted!');
- this.dataTypes[i] = 'text';
+ setState((prev: T) => {
+ return {
+ ...prev,
+ [config.keys[idx]]: txt
}
-
- if (this.dataTypes[i] == 'TINYMCE') {
- input = (
-
-
- this.updateRichText(txt, i)} />
-
- )
- } else {
- input = (
-
-
- this.update(e, i)}
- value={this.state[i as keyof T] as string}>
-
-
- )
- }
-
- output.push(input);
- }
-
- return {output}
;
+ })
}
-}
\ No newline at end of file
+
+ function update(e: ChangeEvent, idx: number) {
+ if (!config) return;
+
+ setState((prev: T) => {
+ return {
+ ...prev,
+ [config.keys[idx]]: e.target['value' as keyof EventTarget]
+ }
+ })
+ }
+
+ // mount the form once config has been loaded
+ useEffect(() => {
+ if (state && config) {
+ const result = config.keys.map((each: string, i: number) => {
+
+ if (config.dataTypes![i] == 'TINYMCE') {
+ return (
+
+
+ updateRichText(txt, i)} />
+
+ )
+ } else {
+ return (
+
+
+ update(e, i)}
+ value={state[i as keyof T] as string}>
+
+
+ )
+ }
+ });
+
+ setContents(result);
+
+ }
+ }, [config]);
+
+ return (
+
+ { contents }
+
+ )
+}
+
+export default Form;
\ No newline at end of file
diff --git a/client/src/components/ui/Navbar/index.tsx b/client/src/components/ui/Navbar/index.tsx
index b15b1db..8e3aca1 100644
--- a/client/src/components/ui/Navbar/index.tsx
+++ b/client/src/components/ui/Navbar/index.tsx
@@ -1,44 +1,25 @@
-import { FC, useCallback, useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
+import { useEffect, useState } from "react";
import { LoggedIn, NotLoggedIn, Registering } from "./variants";
import { useAuthContext } from "../../../context/AuthContext";
-import { IUser } from "../../../schemas";
import "/src/sass/components/Navbar.scss";
-const Navbar: FC<{receiveChange: (change: IUser) => void}> = ({ receiveChange }) => {
+const Navbar = () => {
// setup and local state
- const navigate = useNavigate();
- const { user, setUser } = useAuthContext();
- const [received, setReceived] = useState();
- const [displayed, setDisplayed] = useState();
-
- // lift and store state from navbar variants
- const liftChange = useCallback((newValue: IUser | undefined) => {
- if (!newValue) {
- return;
- }
-
- setUser(newValue);
- setReceived(newValue);
- }, [])
+ const { user } = useAuthContext();
+ const [displayed, setDisplayed] = useState(Loading...
);
const variants = {
- loggedin: ,
- notloggedin: ,
- registering:
+ loggedin: ,
+ notloggedin: ,
+ registering:
}
// side effects for live rendering
useEffect(() => {
- user && setReceived(user);
- }, [user])
+ setDisplayed(user ? variants.loggedin : variants.notloggedin);
+ }, [user]);
- useEffect(() => {
- if (received) receiveChange(received);
- setDisplayed(received ? variants.loggedin : variants.notloggedin);
- }, [received, setReceived]);
-
- return displayed || Loading...
;
+ return displayed;
}
export default Navbar;
\ No newline at end of file
diff --git a/client/src/components/ui/Navbar/variants.tsx b/client/src/components/ui/Navbar/variants.tsx
index a9fa831..923582d 100644
--- a/client/src/components/ui/Navbar/variants.tsx
+++ b/client/src/components/ui/Navbar/variants.tsx
@@ -1,15 +1,26 @@
-import { attemptLogout } from "../../../util/apiUtils";
+import API from "../../../util/API";
import { NavbarType } from "../../../util/types";
-import { Button, Dropdown } from '../.'
-import { useState } from "react";
+import { Button, Dropdown } from '..'
+import { useEffect, useState } from "react";
+import { useAuthContext } from "../../../context/AuthContext";
+import { useNavigate } from "react-router-dom";
+
+const LoggedIn = () => {
+ const { user, setUser, setToken } = useAuthContext();
+ const navigate = useNavigate();
+ const auth = new API.Auth();
-const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
const [dropdownActive, setDropdownActive] = useState(false);
const [searchActive, setSearchActive] = useState(false);
const handleLogout = async () => {
- const success = await attemptLogout();
- if (success) liftChange!(undefined);
+ const success = await auth.logout();
+ console.log(success);
+
+ // nullify cookie and unset user/token data
+ document.cookie = `token=;expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
+ setUser(undefined);
+ setToken(undefined);
navigate('/');
}
@@ -29,6 +40,10 @@ const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
navigate(payload);
}
+ useEffect(() => {
+ console.log(user);
+ }, [])
+
return (
@@ -36,7 +51,7 @@ const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
navigate('/')}>RECIPIN
-
Hi, {received?.firstname}.
+
Hi, {user && user.firstname}.
@@ -64,7 +79,9 @@ const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
)
}
-const NotLoggedIn: NavbarType = ({ navigate }) => {
+const NotLoggedIn = () => {
+ const navigate = useNavigate();
+
return (
@@ -77,14 +94,17 @@ const NotLoggedIn: NavbarType = ({ navigate }) => {
)
}
-const Registering: NavbarType = ({ received, navigate }) => {
+const Registering = () => {
+ const { user } = useAuthContext();
+ const navigate = useNavigate();
+
return (
-
Hi, {received?.firstname}.
+
Hi, {user?.firstname}.
)
diff --git a/client/src/components/ui/Widgets/FriendSearchWidget.tsx b/client/src/components/ui/Widgets/FriendSearchWidget.tsx
index 101c716..d35d308 100644
--- a/client/src/components/ui/Widgets/FriendSearchWidget.tsx
+++ b/client/src/components/ui/Widgets/FriendSearchWidget.tsx
@@ -3,8 +3,12 @@ import { IUser } from "../../../schemas";
import { TextField, UserCard } from "..";
import { v4 } from "uuid";
import { getAllUsers } from "../../../util/apiUtils";
+import API from "../../../util/API";
+import { useAuthContext } from "../../../context/AuthContext";
const FriendSearchWidget: FC<{}> = () => {
+ const { token } = useAuthContext();
+
const [searchTerm, setSearchTerm] = useState
();
const [userPool, setUserPool] = useState([]);
const [friendResults, setFriendResults] = useState([]);
@@ -23,7 +27,10 @@ const FriendSearchWidget: FC<{}> = () => {
// load available user pool on mount
useEffect(() => {
(async function() {
- const result = await getAllUsers();
+ if (!token) return;
+ const users = new API.User(token);
+
+ const result = await users.getAll();
if (result) setUserPool(result);
})();
}, [])
diff --git a/client/src/context/AuthContext.tsx b/client/src/context/AuthContext.tsx
index 25c44bf..32b3dac 100644
--- a/client/src/context/AuthContext.tsx
+++ b/client/src/context/AuthContext.tsx
@@ -2,13 +2,17 @@ import { createContext, Dispatch, SetStateAction, useContext } from "react";
import { IUser } from "../schemas";
export interface IAuthContext {
- user?: IUser
- setUser: Dispatch> | VoidFunction
+ user: IUser | undefined
+ setUser: Dispatch> | VoidFunction
+ token: string | undefined
+ setToken: Dispatch> | VoidFunction
}
export const defaultValue: IAuthContext = {
user: undefined,
- setUser: () => {}
+ setUser: () => {},
+ token: undefined,
+ setToken: () => {}
}
export const AuthContext = createContext(defaultValue);
diff --git a/client/src/context/AuthProvider.tsx b/client/src/context/AuthProvider.tsx
new file mode 100644
index 0000000..c660c76
--- /dev/null
+++ b/client/src/context/AuthProvider.tsx
@@ -0,0 +1,18 @@
+import { FC, Provider, ReactPortal, useEffect, useState } from "react"
+import { IUser } from "../schemas";
+import { AuthContext, IAuthContext } from "./AuthContext";
+
+const AuthProvider = ({ children }: any) => {
+ const [user, setUser] = useState();
+ const [token, setToken] = useState();
+
+ const value = { user, setUser, token, setToken }
+
+ return (
+
+ { children }
+
+ )
+}
+
+export default AuthProvider;
\ No newline at end of file
diff --git a/client/src/main.tsx b/client/src/main.tsx
index bbf6a0e..bec94b7 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -1,6 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
+import AuthProvider from './context/AuthProvider'
import './sass/index.scss'
-ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render()
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+
+
+)
diff --git a/client/src/util/API.ts b/client/src/util/API.ts
new file mode 100644
index 0000000..bf55e63
--- /dev/null
+++ b/client/src/util/API.ts
@@ -0,0 +1,156 @@
+import { AxiosHeaders, AxiosRequestHeaders } from "axios";
+import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList } from "../schemas";
+import { default as _instance } from "./axiosInstance";
+
+module API {
+ export class Settings {
+ private static APISTRING = import.meta.env.APISTRING || "http://localhost:8080";
+ private static token?: string;
+
+ public static getAPISTRING() {
+ return Settings.APISTRING;
+ }
+
+ public static getToken() {
+ return Settings.token;
+ }
+
+ public static setToken(newToken: string) {
+ Settings.token = newToken;
+ }
+ }
+
+ abstract class RestController {
+ protected instance = _instance;
+ protected endpoint: string;
+ protected headers?: any
+
+ constructor(endpoint: string, token: string) {
+ this.endpoint = endpoint;
+ this.headers = {
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": ("Bearer " + token)
+ }
+ };
+ }
+
+ async getAll() {
+ const response = await this.instance.get(this.endpoint, this.headers);
+ return Promise.resolve(response.data);
+ }
+
+ async getByID(id: string) {
+ const response = await this.instance.get(this.endpoint + "/" + id, this.headers);
+ return Promise.resolve(response.data);
+ }
+
+ async post(data: T) {
+ console.log(data);
+ const response = await this.instance.post(this.endpoint, data, this.headers);
+ return Promise.resolve(response.data);
+ }
+
+ async put(id: string, data: T | Partial) {
+ const response = await this.instance.put(this.endpoint + "/" + id, JSON.stringify(data), this.headers);
+ return Promise.resolve(response.data);
+ }
+
+ async delete(id: string) {
+ const response = await this.instance.delete(this.endpoint + '/' + id, this.headers);
+ return Promise.resolve(response.data);
+ }
+ }
+
+ export class Auth {
+ private instance = _instance;
+ private endpoint = Settings.getAPISTRING() + "/auth";
+
+ async login(data: IUserAuth | Partial) {
+ try {
+ const response = await this.instance.post(this.endpoint + "/login", data);
+ return Promise.resolve(response.data);
+ } catch (e: any) {
+ console.error(e);
+ }
+ }
+
+ async register(data: IUser) {
+ try {
+ const response = await this.instance.post(this.endpoint + "/register", data);
+ return Promise.resolve(response.data);
+ } catch (e: any) {
+ console.error(e);
+ }
+ }
+
+ async logout() {
+ try {
+ const response = await this.instance.delete(this.endpoint + '/logout');
+
+ // unset cookie data and send response
+ document.cookie = `token=;expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
+ return Promise.resolve(response.data);
+ } catch(err) {
+ console.error(err);
+ }
+ }
+
+ // for future use
+ async registerGoogle() {
+ return;
+ }
+
+ async loginGoogle() {
+ return;
+ }
+
+ async logoutGoogle() {
+ return;
+ }
+ }
+
+ export class User extends RestController {
+ constructor(token: string) {
+ super(Settings.getAPISTRING() + "/app/users", token);
+ }
+ }
+
+ export class Friendship extends RestController {
+ constructor(token: string) {
+ super(Settings.getAPISTRING() + "/app/friends", token);
+ }
+
+ async getPendingFriendRequests() {
+ const response = await this.instance.get(this.endpoint + "?pending=true", this.headers);
+ return Promise.resolve(response.data);
+ }
+ }
+
+ export class Recipe extends RestController {
+ constructor(token: string) {
+ super(Settings.getAPISTRING() + "/app/recipes", token);
+ }
+ }
+
+ export class Ingredient extends RestController {
+ constructor(token: string) {
+ if (!token) throw new Error("Missing required token");
+ super(Settings.getAPISTRING() + "/app/ingredients", token);
+ }
+ }
+
+ export class Collection extends RestController {
+ constructor(token: string) {
+ super(Settings.getAPISTRING() + "/app/collection", token);
+ }
+ }
+
+ export class GroceryList extends RestController {
+ constructor(token: string) {
+ super(Settings.getAPISTRING() + "/app/grocery-list", token)
+ }
+ }
+}
+
+export default API
\ No newline at end of file
diff --git a/client/src/util/apiUtils.tsx b/client/src/util/apiUtils.tsx
index c4e8a19..8adfaf0 100644
--- a/client/src/util/apiUtils.tsx
+++ b/client/src/util/apiUtils.tsx
@@ -1,5 +1,5 @@
import { ICollection, IUser, IUserAuth } from "../schemas";
-// import { IAuthContext } from "../context/AuthContext";
+import instance from "./axiosInstance";
import axios from "axios";
const API = import.meta.env.APISTRING || "http://localhost:8080";
diff --git a/client/src/util/axiosInstance.ts b/client/src/util/axiosInstance.ts
new file mode 100644
index 0000000..5e8c3fb
--- /dev/null
+++ b/client/src/util/axiosInstance.ts
@@ -0,0 +1,20 @@
+import axios, { AxiosResponse } from 'axios'
+import jwt_decode from 'jwt-decode'
+
+const apiUrl = import.meta.env.VITE_APIURL;
+
+const instance = axios.create({
+ baseURL: apiUrl
+});
+
+instance.interceptors.response.use((res: AxiosResponse) => {
+ if (res?.data.token) {
+ document.cookie = `token=${res.data.token}`;
+ }
+
+ return res;
+}, (err) => {
+ return Promise.reject(err);
+})
+
+export default instance;
\ No newline at end of file
diff --git a/client/src/util/handleToken.ts b/client/src/util/handleToken.ts
new file mode 100644
index 0000000..7f764c7
--- /dev/null
+++ b/client/src/util/handleToken.ts
@@ -0,0 +1,19 @@
+import jwtDecode from "jwt-decode";
+import { IUser } from "../schemas";
+import { TokenType } from "./types";
+
+export default function handleToken(): { token: string, user: IUser } | null {
+ try {
+ const extractedToken: Partial = jwtDecode(document.cookie.split("=")[1]);
+ if (extractedToken) {
+ return {
+ token: document.cookie.split("=")[1],
+ user: extractedToken.user as IUser
+ }
+ }
+ } catch(e: any) {
+ console.log(e.message);
+ }
+
+ return null;
+}
\ No newline at end of file
diff --git a/client/src/util/types.ts b/client/src/util/types.ts
index 2cdd098..ff5ffea 100644
--- a/client/src/util/types.ts
+++ b/client/src/util/types.ts
@@ -46,4 +46,8 @@ export type ButtonComponent = FC
export type ProtectPortal = FC
export type UserCardType = FC
export type NavbarType = FC
-export type CheckboxType = FC
\ No newline at end of file
+export type CheckboxType = FC
+
+export interface TokenType {
+ user: IUser
+}
\ No newline at end of file
diff --git a/server/auth/index.ts b/server/auth/index.ts
index d7880fd..68992d0 100644
--- a/server/auth/index.ts
+++ b/server/auth/index.ts
@@ -20,12 +20,25 @@ export default class AuthService {
// not allowed to use email address that already exists
const user = await UserInstance.getOneByEmail(data.email);
- if (user) throw createError('409', 'Email already in use');
+ if (user) {
+ return new ControllerResponse(StatusCode.Conflict, "Email already in use", false);
+ }
+
+ // check that all required fields are populated
+ let missingFields = new Array();
+ let requiredFields: Array = ['firstname', 'lastname', 'handle', 'email', 'isadmin', 'password'];
+ for (let field of requiredFields) {
+ if (!(field in data)) {
+ missingFields.push(field as string);
+ }
+ }
+
+ if (missingFields.length) {
+ return new ControllerResponse(StatusCode.BadRequest, `Missing fields in output: ${missingFields.join(", ")}`, false);
+ }
// hash password and create new user record
const salt = await bcrypt.genSalt(12);
- console.log(salt);
- console.log(data.password);
bcrypt.hash(data.password!, salt, (err, hash) => {
if (err) throw err;
@@ -37,7 +50,7 @@ export default class AuthService {
UserInstance.post(newData);
})
- return true;
+ return new ControllerResponse(StatusCode.NewContent, "registered successfully", true);
} catch (e: any) {
throw new Error(e);
}
diff --git a/server/auth/middlewares.ts b/server/auth/middlewares.ts
index 231cb98..dd09d7c 100644
--- a/server/auth/middlewares.ts
+++ b/server/auth/middlewares.ts
@@ -1,11 +1,23 @@
-import e, { NextFunction, Request, Response } from "express"
-import ControllerResponse from "../util/ControllerResponse";
-import { StatusCode } from "../util/types";
+import { NextFunction, Request, Response } from "express"
+import dotenv from "dotenv";
+import { IUser } from "../schemas";
+
+dotenv.config();
export function restrictAccess(req: Request, res: Response, next: NextFunction) {
if (req.session.user == undefined) {
- console.log("restricted")
- res.send(undefined);
+ res.send("content restricted");
+ } else {
+ next();
+ }
+}
+
+export function requireSessionSecret(req: Request, res: Response, next: NextFunction) {
+ const secret = process.env.SESSIONSECRET;
+
+ if (!secret) {
+ res.sendStatus(500);
+ throw new Error("Express secret is undefined");
} else {
next();
}
@@ -20,5 +32,11 @@ export function checkFriendStatus(req: Request, res: Response, next: NextFunctio
}
export function checkIsAdmin(req: Request, res: Response, next: NextFunction) {
-
+ const user: IUser | undefined = req.user as IUser;
+
+ if (user.isadmin) {
+ next();
+ } else {
+ res.status(403).send("Unauthorized");
+ }
}
\ No newline at end of file
diff --git a/server/index.ts b/server/index.ts
index 902c8c2..9727fd8 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -1,13 +1,11 @@
import express from 'express';
-import cors from 'cors';
import dotenv from 'dotenv';
import { loaders } from './loaders';
dotenv.config();
-const port = 8080;
+const port = process.env.PORT || 8080;
const app = express();
-app.use(cors());
async function main() {
await loaders(app);
diff --git a/server/loaders/express.ts b/server/loaders/express.ts
index 9537efd..9092b0d 100644
--- a/server/loaders/express.ts
+++ b/server/loaders/express.ts
@@ -1,4 +1,4 @@
-import { Express } from 'express';
+import express, { Express } from 'express';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import morgan from 'morgan';
@@ -6,39 +6,31 @@ import cors from 'cors';
import session from 'express-session';
import pgSessionStore from '../db/sessionStore';
import { IUser } from '../schemas';
+import { requireSessionSecret } from '../auth/middlewares';
-declare module "express-session" {
+const origin = process.env.ORIGIN || 'http://localhost:5173';
+const secret = process.env.SESSIONSECRET;
+
+declare module 'express-session' {
interface SessionData {
- user: IUser
+ user?: IUser
}
}
export const expressLoader = async (app: Express) => {
- app.use(cors({
- origin: process.env.ORIGIN || 'http://localhost:5173',
- credentials: true
- }));
-
- app.use(bodyParser.json());
- app.use(bodyParser.urlencoded({ extended: true }));
+ app.use(cors({ origin: origin }));
+ app.use(express.json());
+ app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
-
- // app.options("*", cors({ origin: 'http://localhost:5173', optionsSuccessStatus: 200 }));
- // app.use(cors({ origin: "http://localhost:5173", optionsSuccessStatus: 200 }));
-
app.use(morgan('tiny'));
-
- app.get('/', (req, res) => {
- res.cookie('name', 'express').send('cookie set');
- })
-
- const secret = process.env.SESSIONSECRET as string;
+ app.use(requireSessionSecret);
app.use(session({
- secret: secret,
+ secret: secret as string,
cookie: {
maxAge: 8 * 60 * 60 * 1000,
- secure: false
+ secure: false,
+ httpOnly: false
},
resave: false,
saveUninitialized: false,
diff --git a/server/loaders/index.ts b/server/loaders/index.ts
index 396365d..0b6837f 100644
--- a/server/loaders/index.ts
+++ b/server/loaders/index.ts
@@ -6,7 +6,7 @@ import { passportLoader } from './passport';
export const loaders = async (app: Express) => {
const expressApp = await expressLoader(app);
- const passportApp = await passportLoader(expressApp);
+ await passportLoader(expressApp);
await swaggerLoader(expressApp);
- await routes(expressApp, passportApp);
+ await routes(expressApp);
}
\ No newline at end of file
diff --git a/server/loaders/passport.ts b/server/loaders/passport.ts
index 92876f5..0bca409 100644
--- a/server/loaders/passport.ts
+++ b/server/loaders/passport.ts
@@ -1,32 +1,35 @@
-import { Strategy as LocalStrategy } from "passport-local";
import passport from "passport";
import { Express } from "express";
-import AuthService from "../auth";
-import { IUserAuth } from "../schemas";
-const AuthInstance = new AuthService();
+import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt";
export const passportLoader = async (app: Express) => {
app.use(passport.initialize());
app.use(passport.session());
- passport.serializeUser((user, done) => {
- done(null, user);
+ passport.serializeUser((user: Express.User, done) => {
+ process.nextTick(() => {
+ done(null, user);
+ })
})
- passport.deserializeUser((user: IUserAuth, done) => {
- done(null, user);
+ passport.deserializeUser((user: Express.User, done) => {
+ process.nextTick(() => {
+ done(null, user);
+ })
})
- // sign in method with passport local strategy
- passport.use(new LocalStrategy({
- usernameField: 'email',
- passwordField: 'password'
- }, async (email, password, done) => {
+ // config for jwt strategy
+ let opts = {
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+ secretOrKey: 'secret'
+ }
+
+ // jwt strategy
+ passport.use(new JwtStrategy(opts, async (token, done) => {
try {
- const response = await AuthInstance.login({ email, password });
- return done(null, response);
- } catch (e: any) {
- return done(e);
+ return done(null, token.user);
+ } catch (error) {
+ done(error);
}
}))
diff --git a/server/package.json b/server/package.json
index 8870e6b..97cd52c 100644
--- a/server/package.json
+++ b/server/package.json
@@ -5,7 +5,7 @@
"main": "dist/index.js",
"scripts": {
"build": "bash util/build.sh",
- "seed": "npm run build && ts-node-dev db/seed.ts",
+ "seed": "npm run build && ts-node --files db/seed.ts",
"dev": "bash util/dev.sh",
"prod": "npm run build && node dist/index.js",
"test": "jest --coverage",
@@ -15,7 +15,6 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@types/cookie-parser": "^1.4.3",
"bcrypt": "^5.1.0",
"body-parser": "^1.20.1",
"connect-pg-simple": "^8.0.0",
@@ -28,8 +27,10 @@
"helmet": "^6.0.0",
"http-errors": "^2.0.0",
"js-yaml": "^4.1.0",
+ "jsonwebtoken": "^9.0.0",
"morgan": "^1.10.0",
"passport": "^0.6.0",
+ "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.8.0",
"pg-promise": "^10.15.0",
@@ -38,16 +39,18 @@
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/connect-pg-simple": "^7.0.0",
+ "@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.14",
- "@types/express-session": "^1.17.5",
+ "@types/express-session": "^1.17.6",
"@types/http-errors": "^2.0.1",
"@types/jest": "^29.2.4",
"@types/js-yaml": "^4.0.5",
"@types/morgan": "^1.9.3",
"@types/node": "^18.11.9",
"@types/passport": "^1.0.11",
+ "@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.34",
"@types/pg": "^8.6.5",
"@types/pg-promise": "^5.4.3",
@@ -57,6 +60,7 @@
"nodemon": "^2.0.20",
"supertest": "^6.3.3",
"ts-jest": "^29.0.3",
+ "ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"tslint": "^6.1.3",
"typescript": "^4.9.3"
diff --git a/server/routes/auth.ts b/server/routes/auth.ts
index 775d88c..43c0be2 100644
--- a/server/routes/auth.ts
+++ b/server/routes/auth.ts
@@ -1,5 +1,6 @@
-import { Express, Request, Router } from "express"
+import { Express, Router } from "express"
import { PassportStatic } from "passport";
+import jwt from "jsonwebtoken";
import { IUser, IUserAuth } from "../schemas";
import AuthService from "../auth";
import { UserCtl } from "../controllers";
@@ -12,18 +13,9 @@ const UserInstance = new UserCtl();
const router = Router();
-export const authRoute = (app: Express, passport: PassportStatic) => {
+export const authRoute = (app: Express) => {
app.use('/auth', router);
- // router.use((req, res, next) => {
- // console.log(req.isAuthenticated());
- // console.log(req.session.user);
- // console.log(req.cookies);
- // console.log();
-
- // next();
- // })
-
router.use((req, res, next) => {
console.log(req.session);
next();
@@ -49,7 +41,7 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
res.status(200).send({ message: "Cool restricted content!" });
})
- router.post('/login', passport.authenticate('local'), async (req, res, next) => {
+ router.post('/login', async (req, res, next) => {
try {
const data: IUserAuth = req.body;
console.log(data);
@@ -59,19 +51,29 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
if (response.ok) {
const user = response.data as IUser;
- req.session.regenerate((err) => {
- if (err) next(err);
- req.session.user = user;
+ req.user = user;
+ req.session.user = user;
- req.session.save((err) => {
- if (err) return next(err);
- })
+ const safeUserData = {
+ id: user.id,
+ firstname: user.firstname,
+ lastname: user.lastname,
+ handle: user.handle,
+ email: user.email,
+ datecreated: user.datecreated,
+ datemodified: user.datemodified
+ }
+
+ const token = jwt.sign({ user: safeUserData }, process.env.SESSIONSECRET as string);
+
+ req.session.save((err) => {
+ return next(err);
})
- res.cookie('userid', user.id, { maxAge: 1000 * 60 * 60 * 24 });
+ console.log(req.session);
- res.send(response);
- res.end();
+ res.cookie('token', token, { httpOnly: true });
+ res.json({ token });
} else {
res.status(401).send({ message: "Login unsuccessful" });
}
@@ -82,10 +84,11 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
router.post('/register', async (req, res, next) => {
try {
- const data = req.body;
+ const data: IUser = req.body;
const response = await AuthInstance.register(data);
- if (!response) res.status(400).send({ ok: false });
- res.status(200).send({ ok: true });
+ response.represent();
+
+ res.status(response.code).send({ ok: response.ok, message: response.data });
} catch(e) {
next(e);
}
@@ -93,11 +96,9 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
router.delete('/logout', async (req, res, next) => {
try {
- req.session.destroy((err) => {
- if (err) throw err;
- })
- res.clearCookie('userid');
- res.status(204).send({ ok: true });
+ res.clearCookie('connect.sid').clearCookie('token');
+ res.status(204).send("logout successful");
+ res.end();
} catch(e) {
next(e);
}
diff --git a/server/routes/collection.ts b/server/routes/collection.ts
index 28c62b1..5682ff9 100644
--- a/server/routes/collection.ts
+++ b/server/routes/collection.ts
@@ -1,14 +1,20 @@
import { Express, Router } from "express";
-import { restrictAccess } from "../auth/middlewares";
+import { checkIsAdmin, restrictAccess } from "../auth/middlewares";
import CollectionCtl from "../controllers/CollectionCtl";
const CollectionInstance = new CollectionCtl();
const router = Router();
export const collectionRoute = (app: Express) => {
- app.use('/collection', router);
+ app.use('/app/collection', router);
- router.get('/:id', restrictAccess, async (req, res, next) => {
+ 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 {
const { code, data } = await CollectionInstance.getOne(id);
@@ -19,7 +25,7 @@ export const collectionRoute = (app: Express) => {
})
// implement is admin on this route
- router.get('/', restrictAccess, async (req, res, next) => {
+ router.get('/', checkIsAdmin, async (req, res, next) => {
try {
const { code, data } = await CollectionInstance.getAll();
res.status(code).send(data);
@@ -28,9 +34,9 @@ export const collectionRoute = (app: Express) => {
}
})
- router.post('/', restrictAccess, async (req, res, next) => {
+ router.post('/', async (req, res, next) => {
const data = req.body;
- console.log(data);
+ console.log(req.body ?? "sanity check");
try {
const result = await CollectionInstance.post(data);
diff --git a/server/routes/course.ts b/server/routes/course.ts
index 5882e2f..a8066dd 100644
--- a/server/routes/course.ts
+++ b/server/routes/course.ts
@@ -4,7 +4,7 @@ const CourseInstance = new CourseCtl();
const router = Router();
export const courseRouter = (app: Express) => {
- app.use('/course', router);
+ app.use('/app/course', router);
router.get('/', async (req, res, next) => {
try {
diff --git a/server/routes/friend.ts b/server/routes/friend.ts
index f1d6353..42037a0 100644
--- a/server/routes/friend.ts
+++ b/server/routes/friend.ts
@@ -7,7 +7,7 @@ const UserInstance = new UserCtl();
const router = Router();
export const friendRouter = (app: Express) => {
- app.use('/friend', router);
+ app.use('/app/friend', router);
router.use((req, res, next) => {
let test = req.session.user;
diff --git a/server/routes/groceryList.ts b/server/routes/groceryList.ts
index b5da130..6dcf2a7 100644
--- a/server/routes/groceryList.ts
+++ b/server/routes/groceryList.ts
@@ -5,7 +5,7 @@ const groceryinstance = new GroceryListCtl();
const router = Router();
export const groceryListRoute = (app: Express) => {
- app.use('/grocery-list', router);
+ app.use('/app/grocery-list', router);
router.get('/', async (req, res, next) => {
const userid = req.query.userid as string;
diff --git a/server/routes/index.ts b/server/routes/index.ts
index 1a82f7e..039b797 100644
--- a/server/routes/index.ts
+++ b/server/routes/index.ts
@@ -1,5 +1,6 @@
+import jwt from "jsonwebtoken";
+import dotenv from 'dotenv';
import { Express } from "express"
-import { PassportStatic } from "passport";
import { userRoute } from "./users";
import { recipeRoute } from "./recipe";
import { collectionRoute } from "./collection";
@@ -11,14 +12,39 @@ import { friendRouter } from "./friend";
import { cuisineRouter } from "./cuisine";
import { courseRouter } from "./course";
-export const routes = async (app: Express, passport: PassportStatic) => {
+dotenv.config();
+
+export const routes = async (app: Express) => {
+ // unprotected routes
+ authRoute(app);
+
+ // 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");
+ } else {
+ jwt.verify(token, process.env.SESSIONSECRET as string, (err, data) => {
+ if (err) {
+ res.status(403).send(err);
+ } else {
+ console.log(data);
+ req.user = data;
+ next();
+ }
+ })
+ }
+ })
+
+ // protected routes
userRoute(app);
friendRouter(app);
recipeRoute(app);
ingredientRoute(app);
-
- // to do: refactor for ctlresponse
- authRoute(app, passport);
collectionRoute(app);
subscriptionRoute(app);
groceryListRoute(app);
diff --git a/server/routes/ingredient.ts b/server/routes/ingredient.ts
index 220cb1a..3d8a010 100644
--- a/server/routes/ingredient.ts
+++ b/server/routes/ingredient.ts
@@ -7,7 +7,7 @@ const IngredientInstance = new IngredientCtl();
const router = Router();
export const ingredientRoute = (app: Express) => {
- app.use('/ingredient', router);
+ app.use('/app/ingredient', router);
router.get('/', async (req, res, next) => {
try {
diff --git a/server/routes/recipe.ts b/server/routes/recipe.ts
index 93fdc65..add3ea5 100644
--- a/server/routes/recipe.ts
+++ b/server/routes/recipe.ts
@@ -8,7 +8,7 @@ const recipectl = new RecipeCtl();
const router = Router();
export const recipeRoute = (app: Express) => {
- app.use('/recipe', router);
+ app.use('/app/recipe', router);
router.get('/:id', async (req, res, next) => {
const { id } = req.params;
diff --git a/server/routes/subscription.ts b/server/routes/subscription.ts
index 0ca65e3..d887154 100644
--- a/server/routes/subscription.ts
+++ b/server/routes/subscription.ts
@@ -5,7 +5,7 @@ const CollectionInstance = new CollectionCtl();
const router = Router();
export const subscriptionRoute = (app: Express) => {
- app.use('/subscription', router);
+ app.use('/app/subscription', router);
router.get('/', async (req, res, next) => {
// @ts-ignore
diff --git a/server/routes/users.ts b/server/routes/users.ts
index 417f3d0..ff64789 100644
--- a/server/routes/users.ts
+++ b/server/routes/users.ts
@@ -6,7 +6,7 @@ const router = Router();
const userCtl = new UserCtl();
export const userRoute = (app: Express) => {
- app.use('/users', router);
+ app.use('/app/users', router);
// get all users
router.get('/', async (req, res) => {
diff --git a/server/schemas/index.ts b/server/schemas/index.ts
index 03fc6f2..2546a8e 100644
--- a/server/schemas/index.ts
+++ b/server/schemas/index.ts
@@ -19,7 +19,7 @@ export interface IUser extends HasHistory, CanDeactivate {
handle: string
email: string
isadmin: boolean
- password?: string
+ password: string
}
export interface IUserAuth {
diff --git a/server/util/ControllerResponse.ts b/server/util/ControllerResponse.ts
index 7bdb4ee..c72fa98 100644
--- a/server/util/ControllerResponse.ts
+++ b/server/util/ControllerResponse.ts
@@ -8,10 +8,14 @@ export default class ControllerResponse implements CtlResponse {
constructor(code: StatusCode, data: T | string, ok?: boolean) {
this.code = code
this.data = data
- this.ok = ok || (this.data !== null)
+ this.ok = ok ?? (this.data !== null)
}
send() {
return { ok: this.ok, code: this.code, data: this.data }
}
+
+ represent() {
+ console.log({ ok: this.ok, code: this.code, data: this.data });
+ }
}
\ No newline at end of file
diff --git a/server/util/dev.sh b/server/util/dev.sh
index a633e84..1c9e33f 100644
--- a/server/util/dev.sh
+++ b/server/util/dev.sh
@@ -1,3 +1,3 @@
#! /bin/bash
-rm -rf dist && mkdir -p dist && cp ./swagger.yaml ./dist && ./node_modules/.bin/tsc --project ./tsconfig.json --watch & ts-node-dev index.ts
+rm -rf dist && mkdir -p dist && cp ./swagger.yaml ./dist && ./node_modules/.bin/tsc --project ./tsconfig.json --watch & ts-node --files index.ts
diff --git a/server/util/types.ts b/server/util/types.ts
index 65efb6a..b3a6691 100644
--- a/server/util/types.ts
+++ b/server/util/types.ts
@@ -13,5 +13,6 @@ export enum StatusCode {
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
+ Conflict = 409,
ServerError = 500
}
\ No newline at end of file