Skip to content
This repository was archived by the owner on Sep 12, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
805e4f5
(SERVER) Add integration tests
miguelgimenezgimenez Sep 12, 2023
5bf24f6
(SERVER) update Dependencies
miguelgimenezgimenez Sep 12, 2023
bbf08ad
(SERVER) project configuration (add eslint, tsconfig, jest config..)
miguelgimenezgimenez Sep 12, 2023
ee145b3
(SERVER) refactor to typescript )
miguelgimenezgimenez Sep 12, 2023
912011f
(SERVER) refactor app structure
miguelgimenezgimenez Sep 12, 2023
386ebea
(SERVER) add error handler
miguelgimenezgimenez Sep 12, 2023
4a76b5c
(SERVER) Add Authentication dependencies
miguelgimenezgimenez Sep 13, 2023
b285b26
Add config from env
miguelgimenezgimenez Sep 13, 2023
69c40bf
(SERVER) Add user integration tests
miguelgimenezgimenez Sep 13, 2023
c9b607a
(SERVER) Add user modules
miguelgimenezgimenez Sep 13, 2023
32aa4c8
(SERVER) fix post integration tests
miguelgimenezgimenez Sep 13, 2023
ba3ded4
(SERVER) Auth strategy and middlewares
miguelgimenezgimenez Sep 13, 2023
c8917a8
(SERVER) fix error
miguelgimenezgimenez Sep 13, 2023
c85462b
(SERVER) Add express async middleware and aync handle errors through …
miguelgimenezgimenez Sep 13, 2023
777d278
(SERVER) Add post permission tests
miguelgimenezgimenez Sep 13, 2023
9eec5af
(SERVER) Add post authorizations
miguelgimenezgimenez Sep 13, 2023
a01a5e0
(SERVER) Improve error handling
miguelgimenezgimenez Sep 14, 2023
511d877
(SERVER) Add express user @types
miguelgimenezgimenez Sep 14, 2023
a433409
(SERVER) fix config
miguelgimenezgimenez Sep 14, 2023
3c61157
(SERVER) fix .gitignore
miguelgimenezgimenez Sep 14, 2023
53f0f70
(CLIENT) Add package-lock and .nvmrc file
miguelgimenezgimenez Sep 14, 2023
14da85c
(SERVER) fix tests(authentication header)
miguelgimenezgimenez Sep 16, 2023
c9dcc72
(CLIENT) add user actions and reducers
miguelgimenezgimenez Sep 16, 2023
f09e510
(CLIENT) Add login button to NavBar
miguelgimenezgimenez Sep 16, 2023
7671a94
(CLIENT) Add Authorization Headers to apiCall
miguelgimenezgimenez Sep 16, 2023
077b589
(CLIENT) Fix Post reducer and post list errors
miguelgimenezgimenez Sep 16, 2023
3d640ee
(CLIENT) Add login button to postListPage
miguelgimenezgimenez Sep 16, 2023
c99edba
(CLIENT) Add Acccess Page
miguelgimenezgimenez Sep 16, 2023
bea8340
(CLIENT) fix deelete Post action
miguelgimenezgimenez Sep 16, 2023
26c0690
(CLIENT) fix Decoded token handle expiration
miguelgimenezgimenez Sep 17, 2023
ab9192f
(SERVER) add input sanitization to controller instead of postservice
miguelgimenezgimenez Sep 19, 2023
b699776
update README
miguelgimenezgimenez Sep 24, 2023
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
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ To start the server be sure to have installed mongoDB locally as a service then
```$xslt
cd server
npm i
node index.js
npm run build
npm start
```
If you want to restart the server at any change you can also install nodemon and start the server like this
To run in dev mode
```
cd server
npm i
npm i -g nodemon
nodemon index.js
npm run dev
```
- In the client repository you have the Front-end code of the blog that uses React and Redux.
To start the Front-end
Expand All @@ -30,6 +30,14 @@ To start the Front-end
npm start
```

To run the backend tests(Docker needed)
```
cd server
npm run test:composeup
npm test
```


## Show us your skills :)

Please create a pull request for each exercise, so that we can evaluate the final features' code.
Expand Down
1 change: 1 addition & 0 deletions client/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v16.13.0
18,937 changes: 18,844 additions & 93 deletions client/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"bootstrap": "^4.4.1",
"fetch": "^1.1.0",
"isomorphic-fetch": "^2.2.1",
"jwt-decode": "^3.1.2",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
Expand Down
58 changes: 31 additions & 27 deletions client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
import './App.css';
import { useDispatch } from 'react-redux';
import { Route, BrowserRouter, Switch } from 'react-router-dom';

import './App.css';
import PostListPage from './Post/pages/PostListPage/PostListPage';
import PostDetailPage from './Post/pages/PostDetailPage/PostDetailPage';
import { Provider } from 'react-redux';

import 'bootstrap/dist/css/bootstrap.min.css';
import Navbar from './Nav/components/Navbar';
import AccessPage from './User/pages/AccessPage/AccessPage';
import { checkAuthentication } from './User/UserActions';


const theme = createMuiTheme({
palette: {
Expand All @@ -19,27 +21,29 @@ const theme = createMuiTheme({
});

function App(props) {
return (
<ThemeProvider theme={theme}>
<div className="w-100">
<Navbar />
<div className="w-100 pt-5 mt-5">
<Provider store={props.store}>
<BrowserRouter>
<Switch>
<Route path="/" exact component={PostListPage} />
<Route path="/posts/:cuid/:slug" exact component={PostDetailPage} />
</Switch>
</BrowserRouter>
</Provider>
</div>
</div>
</ThemeProvider>
);
}
const dispatch = useDispatch();

useEffect(() => {
dispatch(checkAuthentication())
}, [])

App.propTypes = {
store: PropTypes.object.isRequired,
};
return (
<ThemeProvider theme={theme}>
<div className="w-100">
<BrowserRouter>
<Navbar />
<div className="w-100 pt-5 mt-5">
<Switch>
<Route path="/" exact component={PostListPage} />
<Route path="/posts/:cuid/:slug" exact component={PostDetailPage} />
<Route path="/access" exact component={AccessPage} />
</Switch>

</div>
</BrowserRouter>
</div>
</ThemeProvider >
);
}

export default App;
20 changes: 19 additions & 1 deletion client/src/Nav/components/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,32 @@ import Toolbar from '@material-ui/core/Toolbar';
import AppBar from '@material-ui/core/AppBar';
import Typography from '@material-ui/core/Typography';
import Link from '@material-ui/core/Link';
import { useDispatch, useSelector } from 'react-redux';
import { Button } from '@material-ui/core';
import { useHistory } from "react-router-dom";
import { logoutRequest } from '../../User/UserActions';

function Navbar() {
const dispatch = useDispatch()
const history = useHistory()
const isAuthenticated = useSelector(state => state.user?.isAuthenticated);
const login = () => history.push('/access');
const logout = () => dispatch(logoutRequest())

console.log()
return (
<AppBar position="fixed">
<Toolbar>
<Toolbar className='justify-content-between'>
<Typography variant="h6" >
<Link href="/" className="text-white">Home</Link>
</Typography>
{history.location.pathname !== '/access' && <Typography variant="h6" >
<Button
onClick={isAuthenticated ? logout : login}
className="text-white">
{isAuthenticated ? "Logout" : "Login"}
</Button>
</Typography>}
</Toolbar>
</AppBar>
);
Expand Down
10 changes: 9 additions & 1 deletion client/src/Post/PostActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export function deletePost(cuid) {

export function deletePostRequest(cuid) {
return (dispatch) => {
return callApi(`posts/${cuid}`, 'delete').then(() => dispatch(deletePost(cuid)));
return callApi(`posts/${cuid}`, "delete").then((res) => {
if (res.ok) {
dispatch(deletePost(cuid));
} else {
if (window.confirm(res.message)) {
return;
}
}
});
};
}
17 changes: 10 additions & 7 deletions client/src/Post/PostReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ const initialState = { data: [] };

const PostReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_POST :
return {
data: [action.post, ...state.data],
};

case ADD_POSTS :
case ADD_POST:
if (action.post) {
return {
data: [action.post, ...state.data],
};
}
return state

case ADD_POSTS:
return {
data: action.posts,
};

case DELETE_POST :
case DELETE_POST:
return {
data: state.data.filter(post => post.cuid !== action.cuid),
};
Expand Down
2 changes: 1 addition & 1 deletion client/src/Post/components/PostList.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function PostList(props) {
<div className="d-flex flex-column w-100">
<h3 className="mt-4">Posts</h3>
{
props.posts.map(post => (
(props.posts || []).map(post => (
<PostListItem
post={post}
key={post.cuid}
Expand Down
17 changes: 8 additions & 9 deletions client/src/Post/pages/PostListPage/PostListPage.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
// Import Components
import PostList from '../../components/PostList';
import PostCreateWidget from '../../components/PostCreateWidget';
// Import Actions
import { addPostRequest, deletePostRequest, fetchPosts } from '../../PostActions';
import Logo from '../../../logo.svg';
import { Link } from '@material-ui/core';

const PostListPage = ({ showAddPost }) => {
const PostListPage = () => {

const dispatch = useDispatch();
const posts = useSelector(state => state.posts.data);
const isAuthenticated = useSelector(state => state.user?.isAuthenticated);

useEffect(() => {
dispatch(fetchPosts());
},[]);
}, []);

const handleDeletePost = post => {
if (confirm('Do you want to delete this post')) { // eslint-disable-line
Expand All @@ -31,16 +32,17 @@ const PostListPage = ({ showAddPost }) => {
<div className="container">
<div className="row">
<div className="col-12 d-flex align-items-center">
<img className="mx-3" src={Logo} alt="Logo" style={{ height: '72px'}}/>
<img className="mx-3" src={Logo} alt="Logo" style={{ height: '72px' }} />
<h1 className="mt-4">
Alaya Blog
Alaya Blog
</h1>
</div>
</div>
<hr />
<div className="row">
<div className="col-6">
<PostCreateWidget addPost={handleAddPost} showAddPost={showAddPost} />
{isAuthenticated ? <PostCreateWidget addPost={handleAddPost} /> : <Link href='/access'>
Login To Create Posts</Link>}
</div>
<div className="col-6">
<PostList handleDeletePost={handleDeletePost} posts={posts} />
Expand All @@ -50,9 +52,6 @@ const PostListPage = ({ showAddPost }) => {
);
};

PostListPage.propTypes = {
showAddPost: PropTypes.bool.isRequired
};


export default PostListPage;
110 changes: 110 additions & 0 deletions client/src/User/UserActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import callApi from '../util/apiCaller';
import jwtDecode from "jwt-decode";

// Export Constants

export const SIGN_UP_SUCCESS = 'SIGN_UP_SUCCESS';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const SIGN_UP_ERROR = 'SIGN_UP_ERROR';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export const UPDATE_AUTHENTICATION = 'UPDATE_AUTHENTICATION';

export function signUpRequest(user) {
return async (dispatch) => {
try {
const res = await callApi("user/signup", "post", {
email: user.email,
password: user.password,
});
localStorage.setItem("accessToken", res.accessToken);
dispatch({
type: SIGN_UP_SUCCESS, payload: {
accessToken: res.accessToken,
user: {
id: res.user._id,
role: res.user.role,
}
}
});
return res;
} catch (error) {
// handle error apropiately like dispatching ({ type: SIGN_UP_ERROR, payload: error.message } and using a snackbar..
window.confirm(res.message)

}
};
}

export function loginRequest(user) {
return async (dispatch) => {
try {
const res = await callApi("user/login", "post", {
email: user.email,
password: user.password,
});
localStorage.setItem("accessToken", res.accessToken);
dispatch({
type: LOGIN_SUCCESS,
payload: {
accessToken: res.accessToken,
id: res.user._id,
role: res.user.role,
}
})
} catch (error) {
// handle error apropiately like dispatching ({ type: SIGN_UP_ERROR, payload: error.message } and using a snackbar..
window.confirm(res.message)

}
};
}

export function logoutRequest() {
return async (dispatch) => {
localStorage.removeItem("accessToken");
return dispatch({
type: UPDATE_AUTHENTICATION,
payload: {
isAuthenticated: false,
user: null,
}
});
};
}

export function checkAuthentication() {
return (dispatch) => {
const accessToken = localStorage.getItem('accessToken');

if (!accessToken) {
return dispatch({
type: UPDATE_AUTHENTICATION,
payload: { isAuthenticated: false, id: null, accessToken: null, role: null }
});
}
const decodedAccessToken = jwtDecode(accessToken);
const currentTime = Date.now()
if (!decodedAccessToken.exp || currentTime > Number(decodedAccessToken.exp) * 1000) {

return dispatch({
type: UPDATE_AUTHENTICATION,
payload: {
isAuthenticated: false,
id: null,
role: null,
},
});
}
// If accessToken is present and not expired, set isAuthenticated to true
dispatch({
type: UPDATE_AUTHENTICATION,
payload: {
isAuthenticated: true,
accessToken,
id: decodedAccessToken.id,
role: decodedAccessToken.role,
}
});
};
}
Loading